EF Code First Modelbuilder creating unique identifiers where it should not - asp.net-mvc

I have something like this:
modelBuilder.Entity<TransactionHistory>()
.HasOptional(history => history.Sender)
.WithMany()
.Map(s => s.MapKey("Sender"))
.WillCascadeOnDelete(false);
modelBuilder.Entity<TransactionHistory>()
.HasOptional(history => history.Receiver)
.WithMany()
.Map(s => s.MapKey("Receiver"))
.WillCascadeOnDelete(false);
And in my table TransactionHistory, it creates unique identifiers at columns Sender and Receiver. I don't want those columns to be unique, what do i do?
TransactionHistory model:
public class TransactionHistory
{
public Account Sender { get; set; }
public Account Receiver { get; set; }
}
Edit: Ok. Apparently uniqueidentifiers are not the case. The problem is, that when i am adding transactionhistory item into database, i got the following error:
Violation of PRIMARY KEY constraint 'PK_dbo.Accounts'. Cannot insert
duplicate key in object 'dbo.Accounts'.\r\nThe statement has been
terminated.
An i add this item like that:
context.Transactions.Add(history);
context.savechanges();
(Transactions is a transactionhistory object)

EF uses uniqueidentifier as SQL column type for the foreign keys because the principal's (= Accounts) primary key is a uniqueidentifier - in C# it is a Guid, like public Guid AccountId { get; set; }.
It must choose this type because principal and dependent key types must match in a foreign key relationship in the database.
This does not mean that the foreign key column is unique (or has a unique index). Of course you can use the same uniqueidentifier value multiple times as the foreign key column value.

Entity framework works by storing a cache of a bunch of objects in DBContext. Even if all the properties are the same on your Account object (including your pk) entity framework will see this as a new object unless you remind it "hey, you already know about this". I can think of 2 ways to do this:
context.Entry(history.Sender).State = EntityState.Modified;
context.Entry(history.Receiver).State = EntityState.Modified;
or
Make sure when you set the Accounts on your history object that they are already attached to your dbcontext.
var sender = context.Accounts.FirstOfDefault(...your condition here...);
var receiver = context.Accounts.FirstOfDefault(...your condition here...);
history.Sender = sender;
history.Receiver = receiver;
Also EF does not load navigation/related entities unless you tell it to. So if you are editing history make sure you using .Include() to pull in your related objects.
Hope that helps.

Related

How to map table without primary key Entity Framework

I'm using Entity Framework to map some tables, but I can't do this unless I declare some column as the primary key.
The problem here is that my table in my database don't have a primary key and has millions of rows. I don't have permission to create a new Id column.
[Table("MYTABLE")]
public class myTable
{
[Column("NUMX")]
public virtual string NumX { get; set; }
[Column("NAME")]
public virtual string Name { get; set; }
[Column("AGE")]
public virtual int AGE { get; set; }
}
Obs: If I add the [Key] attribute to some column like Age, it works, but returns the wrong data.
Is there some way to omit the primary key?
I Figured out the problem.
Composite Keys works for me:
eg:
In my Context I defined some keys, not only one, but three keys:
public class MyContext : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
**//Here is the secret**
modelBuilder.Entity<MyModel>().HasKey(x => new { x.NumX, x.Name,x.Age});
}
}
Entity Framework requires a primary key unlike SQL.
EF use the primary key to uniquely identify rows (for example when you use .Find() or to perform update operations).
Infact not having a primary key remember a SQL VIEW, where you can only read data.
If any of the columns uniquely identify a certain row set it as a primary key (it can't be NULL) also if in Sql it isn't a key.
Otherwise if the combination of the columns are uniquely, create a composite key with these columns.
Remember that you should have a primary key in the 99% of cases, when you don't have a primary key you should stop and think if it make sense.

ASP.MVC db Find(), but with non-primary key parameter

How does one get a list of results by using a key that is not the primary key? To be more specific, I have a composite primary key where I would like to retrieve all the matches with one column's parameter.
I would think, in an ActionResult in the Controller, it's something like
tableModel tbmodel = db.tableModels.Find(i => i.partialPK == parameter)
but that's not the case, since Find() only works with the entire PK.
I declared my PKs in the entity model class as:
[Key]
[Column(Order = 0)]
public int PK1 { get; set; }
[Key]
[Column(Order = 1)]
public string PK2 { get; set; }
According to DbSet.Find you can pass in the primary keys separated by commas
db.tableModels.Find(PK1, PK2)
The Find method takes an array of objects as an argument. When working with composite primary keys, pass the key values separated by commas and in the same order that they are defined in the model.
However, if you want to just use one value, you will probably have to use
db.tableModels.Where(i => i.partialPK == parameter)
or an equivalent Linq operator
If you're trying to get the object with linq query from database, use: .FirstOrDefault with .Where
Customer customer = db.Customers.Where(c => c.Email == auth.Email).FirstOrDefault();
Here db is DbContext
and Customer is the DbSet

EF code first - change generated column name for relationship

Using EF code first want to control the name of the genereted column WHEN REFERENCING SELF. Tried [ForeignKey] and [Column] attributes. [Column] does not work and [ForeignKey] forces a relationship that seems to cause a problem. I have used [ForeignKey] attribute to set the name of a column in another class/table connected to this one.
Have
public class Episodes
{
public long ID {get; set;}
// ... other properties
public List<Episodes> Children { get; set; }
}
want a table (ID, ..., ParentID) - get a table (ID, ..., Episode_ID)
If you don't have the foreign key column exposed as property in your entity class you can define the column name only with Fluent API:
modelBuilder.Entity<Episodes>()
.HasMany(e => e.Children)
.WithOptional()
.Map(m => m.MapKey("ParentID"));
I assume that the parent is optional (ParentID is a nullable column in the DB table) because, I guess, there should be some "root" in the tree, i.e. an episode that doesn't have a parent anymore, hence at least for this episode the ParentID column must be NULL.

single entity for different tables

I have same type of tables
ProductCodeTable, CountrycodeTable etc
All have key, value as their fields
When I use entity frame work,
Can I have a single entity for all these tables such that I can formulate different queries to get data from different tables?
You can create a base class for all of them and create sub class for each entity
public abstract class LookUpEntity
{
[Key]
public int Key { get; set; }
[Required]
public string Value { get; set; }
}
[Table("ProductCodeTable")]
public class ProductCode : LookUpEntity
{
}
This way you can model the relationships also and later if you wanted to add specific properties to those look up entities with out affecting other entities.
You can create a view with a Union of all tables like this:
create view AllKeyTables as
SELECT 'Product' as table, Productkey as Key, nameProduct as name
FROM ProductCodeTable
UNION
SELECT 'Country' as table, CountryISO as key, CountryName as name
FROM CountrycodeTable
UNION
...
Then update EF model and check 'table' and 'key' fields as Entity Primary Key.
Next question you will do is: 'How can I make a relation between this Entity and existing Entities?' The answer is 'you can't because EF is not able to join Entities between other fields than primary key'. Then, before implement this solution, be sure that this is you are looking for.
EF supports this only if you model it as inheritance (#Eranga showed it in code-first approach). You will have single base entity and derived entity for each table so you will not avoid having different type for each table. Otherwise the answer is no.
Inheritance will put additional requirements on data in your tables. For example Ids will have to be unique among all tables. So if ProductTableCode will have record with Id 1, CountryCodeTable (and any other code table) mustn't have record with Id 1.
Also inheritance in EF can produce nasty and very poorly performing queries.

What's wrong with this mapping?

I want to create following table based on below class dictionary. I get exception when I add records. What's wrong with this mapping? This code works if I point Child property of class "B" to another class (example "C").
database table
table A {id, name}
table B {parentId, childId, Type}
Class and Mapping
Public class A
{
public int Id {get;set;}
public string Description {get;set;}
}
Public class B
{
[Key, Column(Order=0)]
public int ParentId {get;set;}
[Foreignkey("ParentId")]
public A Parent {get;set;}
[Key, Column(Order=1)]
public int ChildId {get;set;}
[Foreignkey("ChildId")]
public A Child {get;set;}
[Key, Column(Order=2)]
public string Type {get;set;}
}
UPDATE
Error Message is: Introducing FOREIGN KEY constraint 'B_Parent' on table 'B' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
Could not create constraint. See previous errors.
Thanks,
Ashraf
After reading the following posts I found the solution.
http://weblogs.asp.net/manavi/archive/2011/05/01/associations-in-ef-4-1-code-first-part-5-one-to-one-foreign-key-associations.aspx
Entity Framework Code First - Defining Relationships/Keys
It's the SQL Server error. Class 'A' referencing twice in class 'B'. Code First attempt to turn on cascade delete for both Parent and Child columns in class 'B' which cause the exception.
Fix is manually override one of the cascade option to false in class B. I don't know how to set CascadeOnDelete option as dictionary attribute. But here is the fluent api.
HasRequired(x => x.Parent)
.WithMany()
.HasForeignKey(x => x.ParentId)
.WillCascadeOnDelete(false);
I wish EF team attempt to write a comprehensive guide for Code First configuration (Fluent API) manual for us. AdventureWorks-2008 database is a great candidate for that.
this exception is SQL server specific.It will go away if you turn off the cascade for the relationship.By default Ef will turn it on for you.you can do this through fluent api.
In the relationship just add the following configuration
.WillCascadeOnDelete(false);
hope this helps.

Resources