I am migrating my project from EF 4.0 to EF 6.0.
I am trying to get properties from navigated entity after insert new entity. That code runs without any problem in EF 4.0 but when I try to run in EF 6.0 I am gettin NullReference Exception.
User Entity:
public partial class users
{
public int RID { get; set; }
public string Name { get; set; }
public Nullable<int> LangRef { get; set; }
public virtual language language { get; set; }
}
Language Entity:
public partial class language
{
public language()
{
this.users = new HashSet<users>();
}
public int RID { get; set; }
public string Name { get; set; }
public virtual ICollection<users> users { get; set; }
}
Test Code:
test1Entities testEnt = new test1Entities();
users user = new users();
user.Name = "asd";
user.LangRef = 1;//That ID refer to English record in Language entity
testEnt.users.Add(user);
testEnt.SaveChanges();
string lang = user.language.Name;//Should return "English". However language property seem null
Model:
You say that user.language.Name should return English, you are not setting that anywhere. You have to set the language when you create a new user.
test1Entities testEnt = new test1Entities();
users user = new users();
user.Name = "asd";
user.LangRef = 1;
// grab the english language entity from the database
language englishLanguage = entities.First(o => o.Name == "English");
// set the users language to english
user.language = englishLanuage;
testEnt.users.Add(user);
testEnt.SaveChanges();
Imagine if you were inserting a user with raw SQL, you would run something like this
INSERT INTO users (name, languageId) VALUES ('Foo', 1);
Where 1 is the RID of the record in the language table which maps to the english language. The 1 is a Foreign Key in the users table which maps a user to their language. Same as with the raw SQL you must explicitly tell Entity Framework which language the user has. This is done by querying the DbContext for the language (which returns a tracked entity), then setting that entity to the navigation property.
Alternatively, if you know the primary key of the language record in advance, you can create a new language entity (with the correct RID) and attach it to the DbContext, then set it to the navigation property. This would make it so there is one less query against the database. While this would be a little more effecient, you should do whatever is easier/makes the most sense to you. Get it working, then you can optimize bottlenecks later.
language englishLanguage = new language() { RID = 1, Name = "English" };
testEnt.languages.Attach(language);
user.language = englishLanguage;
testEnt.users.Add(user);
Edit
Given your recent edits, it is now more clear what you are trying to do. I didn't realize that LangRef was your foreign key since it was a decimal and doesn't follow normal naming conventions.
The solution is described in this answer, https://stackoverflow.com/a/18251442/1160036. The problem arises from Entity Framework caching. After SaveChanges() (so that the RID is updated), you can detach and re-pull the user to refresh it.
testEnt.users.Add(user);
testEnt.SaveChanges();
((IObjectContextAdapter)testEnt).ObjectContext.Detach(user);
users user = testEnt.First(o => o.RID == user.RID);
Console.WriteLine(user.language.Name);
Opening a new DbContext works as well, but this does cause another connection to be made to the database.
As described in the above answer, testEnt.Entry<users>(user).Reload() unfortunately doesn't work.
Instead of using new users() try testEnt.users.Create(). The difference is that the object returned will be a proxy class which is needed for lazy loading of navigation properties.
As an aisde, doing the opposite and setting the language navigation property itself and not the LangRef foreign key value should actually correctly set the LangRef property when you call SaveChanges().
See DbSet.Create method on MSDN.
Related
Using Entity Framework, how can I insert data if it does not exist, and update a field if it does?
public class Rootobject
{
public string odatacontext { get; set; }
public Value[] value { get; set; }
}
public class Value
{
public int AccountId { get; set; }
public DateTime? SubmissionDate { get; set; }
public string Status { get; set; }
}
To retrieve all the data from my API I use
root.value.Select(x => new satiaL
{
accountID = x.AccountID,
subDate = x.SubmissionDate,
x_status = x.Status
});
which of course will insert all records.
If the AccountID already exists in the database, I want to update the value of x_status, but if the AccountID does NOT yet exist in the database, then I want to insert all values.
You can not.
Upsert functionality is not part of an object/relational model - objects are there or not, and tracked by identity. Thre is no "update if it is not there" concept - at all. So, there is nothing for EfCore to implement.
This smells like abusing an ORM as a ETL loader, and this is not what you should do - ETL (mass data loading) is not what and ORM is made for. Time to write your own method to move data up into tables and possibly do upswert there. Did that years ago, comes really handy at times.
Right now all you can do is run a lot of finds for every account and basicalyl write code: create if not exists, update if exists.
Pseudocode:
var account = Find ( select ) or default from db
if account == null create
else update
savechanges
Something along this line. Beware of performance - you may want to just builk load all accounts. Beware of conflicting updates.
Problem
I have a situation whereby I need to use Entity Framework 6, Code First, with a legacy database structure which cannot be changed. The database has a very generic table which stores text based data alongside some non key data which can be used to relate the record back to another table.
To illustrate:
Assume the Notes table has a model as follows:
[Table("Notes")]
public class Notes
{
[Key]
public int RecordId { get; set; }
[Required]
public string RelatedTableName { get; set; }
[Required]
public int RelatedTableRecordId { get; set; }
[Required]
public string NotesText { get; set; }
}
I then have another model which could look like so:
[Table("Drivers")]
public class Drivers
{
[Key]
public int RecordId { get; set; }
[Required]
public string DriverName { get; set; }
public ICollection<Notes> DriverNotes { get; private set; }
}
There is no foreign key which links the tables. The Drivers table is linked to the Notes table by way of the RelatedTableName and RelatedTableRecordId fields.
I do not have a problem reading data from the database and hydrating the models using entity framework.
The problem I have is that I want to be able to save a new Driver and its newly created Notes in one transaction and have the RelatedTableRecordId field set to the primary key of the Driver.
If a foreign key existed entity framework would know to back fill the property but in this case it doesn't know about the relationship.
Key Points
Database Structure must not change.
Must use Entity Framework 6 Code First
Must be able to use an Execution Strategy.
Require a relationship between non key fields.
Need to be able to persist all data in a single transaction.
What I've Tried
I had a similar issue with Audit type data and solved it by doing something similar to the following (note that this is very pseudo here):
public override int SaveChanges()
{
int changes = 0;
//Disable the current execution strategy as the default ones do not support user instantiated transactions.
this.ContextConfiguration.SuspendExecutionStrategy();
try
{
//Wrap a whole transaction inside an execution strategy so that auditing can be combined with regular saving of changes.
this.ExecutionStrategy.Execute(
() =>
{
using (var transaction = this.Database.BeginTransaction())
{
//Reset the change count so that it doesn't increase each time the transaction fails.
changes = 0;
//Remove any audit records created by previous failed transactions.
this.AuditTableChanges.Local.Clear();
//Evaluate the change tracker to identify entities which will potentially require an audit trail.
var insertedEntities = this.ChangeTracker.Entries().Where(entryEntity => entryEntity.State == EntityState.Added).ToList();
//Save all changes to get identities.
changes = base.SaveChanges();
//Create the audit trail for inserted entities. This step must occur after the initial call to SaveChanges() so that the identities are set.
foreach (DbEntityEntry entryEntity in insertedEntities)
{
//For each inserted record, get the audit record entries and add them
foreach (AuditTableChange auditTableChange in GetAuditRecords(entryEntity, AuditTableChangeType.Insert).Result)
this.AuditTableChanges.Add(auditTableChange);
}
//Save the audit trail for inserted entities.
changes += base.SaveChanges();
//Commit all changes to the database
transaction.Commit();
}
});
}
finally
{
//Re-enable the execution strategy so that other calls can benefit from the retry policy.
this.ContextConfiguration.UnSuspendExecutionStrategy();
}
return changes;
}
This worked fine for the Audit data as the implementation was hidden away in the framework. I do not want my development team to have to do all of the above each time they persist records.
In its simplistic form this is as much as I'd want people to be doing:
public void CreateDriver()
{
using (MyContext context = new MyContext())
{
Drivers driver = new Drivers();
driver.DriverName = "Joe Bloggs";
Notes driverNote = new Notes();
driverNote.RelatedTableName = "Drivers";
driverNote.NotesText = "Some very long text";
driver.DriverNotes.Add(driverNote);
context.Drivers.Add(driver);
context.SaveChanges();
}
}
In a way I want a foreign key which exists in code but not in the database so that entity framework knows to fill in the RelatedTableRecordId field. I've read some articles on hacking the EDMX but this project is purely Code First only.
There are older questions on stack overflow which are similar but relate to older versions of entity framework and don't help much or have as much detail as the above.
I'm hoping that someone may have experienced a similar problem and has an answer which may involve perhaps some custom mapping/metadata or some overrides to entity framework logic.
Any help would be appreciated.
Thanks,
Greg
SQL Layer:
I have a table
Entity Framwork Layer:
I have the following rule: all Offers, which have State is null, are Outstanding offers, State is true are Accepted offers, State is false are Declined offers. Also, part of fields used only for Outstanding, part - only for Accepted etc... I use Database first approach, so, I updated EF model from DB and renamed Offer entity to OfferBase and created 3 child classes:
it works fine for add/select entities/records. Right now I want to "move" offer from outstanding to accept offer, so, I need to set Status=true (from Status is null) for appropriate record. But how to do it by Entity Framework? If I try to select outstanding offer as Accept offer I get an null reference (and clearly why)
// record with ID=1 exists, but State is null, so, EF can not find this record and offer will be null after the following string
var offer = (from i in _db.OfferBases.OfType<EFModels.OfferAccepted>() where i.ID == 1 select i).FirstOrDefault();
if I try to select as OfferBase entity I get the following error:
Unable to cast object of type
'System.Data.Entity.DynamicProxies.OfferOutstanding_9DD3E4A5D716F158C6875FA0EDF5D0E52150A406416D4D641148F9AFE2B5A16A'
to type 'VTS.EFModels.OfferAccepted'.
var offerB = (from i in _db.OfferBases where i.ID == 1 select i).FirstOrDefault();
var offer = (EFModels.OfferAccepted)offerB;
ADDED NOTES ABOUT ARCHITECTURE:
I have 3 types of Offer entity. There are: AcceptOffer, DeclineOffer and OutstandingOffer.
AcceptOffer:
UserID
ContactID
Notes
FirstContactDate
LastContactDate
[... and 5-10 the unique fields...]
DeclineOffer:
UserID
ContactID
Notes
[... and 5-10 the unique fields...]
OutstandingOffer:
UserID
ContactID
FirstContactDate
LastContactDate
[... and 5-10 the unique fields...]
How to do it correctly? Of course, I can select a record, remove from DB and add new with appropriate state value, but how to do it normally?
You can't change the type of an object once it's created. Your object model seems wrong.
Either you delete the outstanding offer and create an accepted offer from it (looks like what you are currently doing) but you may lose relations as you created a new object with a new identity (you can also copy them before removing the old object). Or you want to keep the same object and change its state.
If you want to keep the same identity then preffer composition over inheritance.
Your code could look like this :
public class Offer
{
public int Id { get; set; }
public virtual OfferState State { get; set }
}
public class OfferState
{
public int OfferId { get; set; }
public string Notes { get; set; }
}
public class AcceptedOfferState : OfferState
{
public DateTimeOffset AcceptDate { get; set; }
}
public class DeclinedOfferState : OfferState
{
public DateTimeOffset DeclinedDate { get; set; }
}
If you still want to change the type of the object and keep its identity then you may use stored procedures ; as stated by Noam Ben-Ami (PM owner for EF) : Changing the type of an entity.
Rather than trying to add these custom classes to your entity framework model, just create them as normal c# classes and then use a projection to convert from the entity framework generated class to your own class e.g.
var accepteOffers= from i in _db.Offers
where i.ID == 1 && i.Status == true
select new OfferAccepted { AcceptDate = i.AcceptDate, StartTime = i.StartTime /* map all releaveant fields here */};
I am using Entity Framework 4.3 Code First, and I have problem with updating many-to-many relationships.
I defined the following classes:
public abstract class Entity
{
[Column(Order = 0)]
[Key]
public int Id { get; set; }
[Timestamp]
[Column(Order = 1)]
public byte[] Version { get; set; }
}
public class Video : Entity
{
public string Title { get; set; }
public string Description { get; set; }
public TimeSpan Length { get; set; }
public virtual ICollection<Coworker> Coworkers { get; set; }
}
public class Coworker : Entity
{
public string FirstName { get; set; }
public string LastName { get; set; }
public virtual ICollection<Video> Videos { get; set; }
}
When the database is created, the schema look right:
There is a Videos, Coworkers and VideoCoworkers table too, without
I use repository pattern in an N-Tier application to access database, my Insert and Update method looks like this:
public T Insert(T entity)
{
//Creates database context. When it disposes, it calls context.SaveChanges()
using (var session = new DatabaseSession())
{
session.Context.Set<T>().Add(entity);
}
}
public T Update(T entity)
{
//Creates database context. When it disposes, it calls context.SaveChanges()
using (var session = new DatabaseSession())
{
entity = session.Context.Set<T>().Attach(entity);
session.Context.Entry(entity).State = EntityState.Modified;
}
return entity;
}
When I update an entity, I create the entity object from a DTO, that's why use DbSet.Attach instead of selecting it and updating the properties one-by-one.
When I initialize the database, I add some test data:
Create 3 Coworkers, where I set first and last name. (A, B, C)
Create 3 Videos, where I set title, description and length, and also set some coworkers. First video has A,B, second has B,C and third has A,C.
When I list the Videos from code, I can see that Video.Coworkers collection is filled with good values, and when I query the link table (VideoCoworkers) in SQL Server Management Studio, it also looks good.
My problem is
when I update for example the title of the Video, it works. But when I try to delete from Video2 the existing coworkers (B and C), and try to add coworker A, then the relationship is not updated. It also does not work when I only try to add new coworker, or only try to delete one. I create the entity which is used as the parameter of the Update() method by creating a new Video entity with a new collection of Coworkers (which are selected from the database with Find() method by Id).
What is the correct way to update many-to-many relationships?
But when I try to delete from Video2 the existing coworkers (B and C),
and try to add coworker A, then the relationship is not updated.
Without using a generic repository the correct procedure would be:
using (var session = new DatabaseSession())
{
video2 = session.Context.Set<Video>().Include(v => v.Coworkers)
.Single(v => v.Id == video2Id);
coworkerA = new Coworker { Id = coworkerAId };
session.Context.Set<Coworker>().Attach(coworkerA);
video2.Coworkers.Clear();
video2.Coworkers.Add(coworkerA)
session.Context.SaveChanges();
}
The essential part is that you must load or attach the entity in its original state, change the entity, i.e. remove and add children, and then save the changes. EF's change detection will create the necessary INSERT and DELETE statements for the link table entries. The simple procedure to set the state to Modified you are trying in your generic Update method is suited only for updating scalar properties - like changing the video title - but won't work for updating relationships between entities.
For solve this problem:
attach the entity to context
load the collection(the collection is not loaded, because )
change the state of entity to modified
save changes
So your code for update should be like this:
public Video Update(Video entity)
{
//Creates database context. When it disposes, it calls context.SaveChanges()
using (var session = new DatabaseSession())
{
entity = session.Context.Set<Video>().Attach(entity);
session.Context.Entry(entity).Collection(p => p.Coworkers).Load();
session.Context.Entry(entity).State = EntityState.Modified;
}
return entity;
}
Please refer here to see how to save master detail in asp.net mvc with database first. Hopefully it will give you the idea about the code first. You may also have a look at knokout.js example
I'm having a very odd issue with SubSonic where when I edit a class the database isn't being updated, even when I delete it and regenerate it.
Example: Simple class
public class Customer {
public Guid Id { get; set; }
public string Description { get; set; }
}
Customer c = new Customer() { Id = Guid.NewGuid(), Description = "Toaster" };
var repo = new SimpleRepository("CustomerTest",
SimpleRepositoryOptions.RunMigrations);
repo.Add(c);
If I run this code it works perfectly, creates a table "Customer" and inserts the row for the toaster. However if I decide to change my Customer class to:
public class Customer {
public Guid Id { get; set; }
public string Description { get; set; }
public int Cost { get; set;}
}
And run the same code adding a value for the Cost property the database table remains "Id, Description". If I create a totally new class and past in the Customer fields it will create the table correctly the first time and again any changes dont appear to work.
Any help?
First off all, you should try to figure out if subsonic detects your class definition changes properly.
This code should give you a overview of the statements subsonic want's to execute.
var migrator=new SubSonic.Schema.Migrator(Assembly.GetExecutingAssembly());
var provider=ProviderFactory.GetProvider("CustomerTest");
string[] commands=migrator.MigrateFromModel<Customer>(provider);
commands should contain all changes subsonic wants to make to your database.
You can execute these commands by yourself with:
BatchQuery query = new BatchQuery(provider);
foreach(var s in commands)
query.QueueForTransaction(new QueryCommand(s.Trim(), provider));
//pop the transaction
query.ExecuteTransaction();
(code taken from http://subsonicproject.com/docs/3.0_Migrations).
That said, I suppose commands will be empty in your case.
In that case that could be caused by a statement that is not implemented by the provider you are using (SqlServer/MySQL/SQLite/Oracle). Maybe you should download the SubSonic source and step into the migrator.MigrateFromModel(...) method to see what happens.
Another possible cause (if you use MySQL) could be that your information schema is not up to date. I encountered this problem a while ago. After changing my database and regenerating the DAL with SubSonic 2, my generated code didn't change.
I figured out that the mysql information schema (and subsonic does queries on the information schema) hadn't changed yet.
I solved this by executing FLUSH TABLES which caused the information scheme to reload. I don't know if that's a bug in mysql or desired behaviour but you should try FLUSH TABLES first.