I have a solution which uses Entity Framework to insert invoices to a database table. These invoices reference an order, which in turn also references an order item collection.
In this instance I am trying to add an order to the database, however the code is inside a new DbContext and so I need to attach the order and order items to the context, as these already exist in the database and shouldn't be re-added.
I've cut down the model properties for the sake of demonstration:
public class Invoice {
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int InvoiceId { get; set; }
public string OrderNumber { get; set; }
...
public virtual List<InvoiceLineItem> LineItems { get; set; }
}
public class InvoiceLineItem {
[Key]
public int Id { get; set; }
...
public ShopifyOrderItem { get; set; }
}
public class ShopifyOrder {
[Key, DatabaseGenerated(DatabaseGeneratedOption.None)]
public long Id { get; set; }
public int OrderNumber { get; set; }
...
public OrderInvoiceStatus InvoiceStatus { get; set; }
public virtual List<ShopifyOrderItem> OrderItems { get; set; }
}
public class ShopifyOrderItem {
[Key, DatabaseGenerated(DatabaseGeneratedOption.None)]
public long Id { get; set; }
...
[Required]
public virtual ShopifyOrder ShopifyOrder { get; set; }
}
In the invoice engine, I'm running the following code for each invoice to add it to the database:
ShopifyOrder order = await db.ShopifyOrders.SingleOrDefaultAsync(x => x.OrderNumber.ToString() == inv.OrderNumber);
if (order != null) {
// Attach marketplace entity to the invoice to avoid duplicate primary key exceptions
db.Marketplaces.Attach(inv.Marketplace);
db.Invoices.Add(inv);
order.InvoiceStatus = OrderInvoiceStatus.InProgress;
}
I've tried a number of methods to try and attach the states, however they all throw errors.
inv.LineItems.ForEach(li => {
db.Entry(li).State = EntityState.Unchanged;
db.Entry(li.ShopifyOrderItem).State = EntityState.Unchanged;
db.Entry(li.ShopifyOrderItem.ShopifyOrder).State = EntityState.Modified;
});
The above code returns the following error on save:
EntityFramework: Saving or accepting changes failed because more than one entity of type 'TorroModels.ShopifyOrder' have the same primary key value. Ensure that explicitly set primary key values are unique. Ensure that database-generated primary keys are configured correctly in the database and in the Entity Framework model.
What is the best way to attach the LineItems/ShopifyOrderItems without trying to attach the ShopifyOrder connected property multiple times?
Sorry to say but it seems that you need to follow the best practice first when constructing a relationship. You may follow this link :
http://www.entityframeworktutorial.net/entity-relationships.aspx
In short :
Avoid using only "Id" in every entity, or you can use attributes to map between the physical name and the property name
It seems that you have circular references here, so maybe you could simplify it first
Next, you can read this link :
http://www.entityframeworktutorial.net/EntityFramework5/attach-disconnected-entity-graph.aspx
if you need to know more about what's the best practice of attaching entities, but in my opinion, just don't abuse this feature, because using normal CRUD should be sufficient most of the time.
I'm sorry I cannot help you more than this, because of lack of information I may need, and with my reputation I still cannot comment directly in your post to ask for it.
Related
I'm working to create a c# application, and in a portion of the application; I'm looking to bring in a .csv to a data table; and then basically loop through each row and query a database to see if the data exists.
I'm testing a LINQ query; but I can't seem to get it to run and display anything. I have the following code setup to run below:
I have the database added and the connection tests succesfully; I have the classes setup. I've been following some courses on pluralsight to test; and I'm not sure what exactly I am doing wrong or missing.
Also as a note; the table name is actually ERP.PartTran, and not PartTran, but I wasn't succesful in setting that up for the db context; could that be why?
EDIT: Code added; images removed
public class EpiDB : DbContext
{
public DbSet<Tran> PartTran { get; set; }
}
public class Tran
{
public int TranNum { get; set; }
public string TranReference { get; set; }
public string PartNum { get; set; }
}
private static void QueryPartTran()
{
var db = new EpiDB();
int tranref = 4650374; //lookup number
var query = from Tran in db.PartTran
where Tran.TranNum == tranref
orderby Tran.TranNum
select Tran;
foreach (var Tran in query)
{
Debug.Print(Tran.PartNum);
}
}
If you have an existing database schema, the first thing to avoid soft exceptions is to disable schema creation/migration in EF. By default when EF connects to a database and goes to resolve the schema, if it comes across a table that it cannot resolve, it will create it. The clue I see that might be happening in this case is when you say the table is called [ERP].PartTran. I suspect you may find that your database has a new empty table called [dbo].Tran. (assuming SQL Server)
To disable schema creation:
In your Db Context constructor
public EpiDB()
{
Database.SetInitializer<EpiDB>(null);
}
This may go a long ways to identifying any bad schema assumptions that EF is making by convention. Jim's answer would be along the lines of where I would believe your problem will lie.
Entities should map relatively closely, if not identically to your table. Renaming an entity or properties to differ from the table to clarify it in code is fine, but you need to be sure that when you do this, you give EF enough information about your schema so that it can resolve the table correctly. If your table is named "PartTran" and your DbSet instance is named "PartTran", why would you want to name the entity "Tran" rather than "PartTran"?
If your application schema is "ERP" then you can avoid needing to specify the schema name on each entity by adding the following to your DbContext.OnModelCreating():
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.HasDefaultSchema("ERP");
// ...
}
Otherwise, if you are using multiple schemas then you will need to explicitly map the schema to use with a [Table] attribute or ToTable("{tableName}", "{SchemaName}") in EntityTypeConfig / modelBuilder config.
Next, ensure that your entity fields match the appropriate fields in the table. You don't need to map every field if you don't need them, but at a minimum you do need to map the Primary Key. On a guess from the PartTran entity, I'm guessing you're either missing something like a PartTranId column, or the PK is a composite key using the PartNum and TranNum columns. If you have a PartTranId or similar PK, add it to the entity along with a [Key] attribute. If the PK is a composite:
public class PartTran
{
[Key, Column(Order = 1)]
public int TranNum { get; set; }
public string TranReference { get; set; }
[Key, Column(Order = 2)]
public string PartNum { get; set; }
}
This should give you a few ideas to check out against your code base... To go further it would help to amend your question to include the related tables and any entities you have tried creating so far. Something like "PartTran" looks like a joining table for a many-to-many relationship between a "Part" table and a "Tran"(saction?) table. If that is the case there are a number of options how you can efficiently wire this up in EF to get the data out the way you want.
Try this:
public class EpiDB : DbContext
{
public DbSet<Tran> PartTran { get; set; }
}
[Table("PartTran", Schema = "ERP")]
public class Tran
{
public int TranNum { get; set; }
public string TranReference { get; set; }
public string PartNum { get; set; }
}
And maybe even:
public class EpiDB : DbContext
{
public DbSet<Tran> PartTran { get; set; }
}
[Table("PartTran", Schema = "ERP")]
public class Tran
{
[Key] // Is this your primary key field?
public int TranNum { get; set; }
public string TranReference { get; set; }
public string PartNum { get; set; }
}
Sorry to raise this one again - I see it plenty of times on here but I am still puzzled about it in my case, especially because I seem to be able to 'overcome' it, but I'm not sure I like how, and I would love to understand why.
I have a many to many relationship between Staff and Departments, with the StaffDepartment table being set up with a composite key like so:
modelBuilder.Entity<StaffDepartment>()
.HasKey(sd => new { sd.StaffId, sd.DepartmentId });
I suspect that must be something to do with the exception I sometimes get:
InvalidOperationException: The instance of entity type 'StaffDepartment' cannot be tracked because another instance with the same key value for {'StaffId', 'DepartmentId'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached.
I say sometimes because, strangely, if I change the department in my staff details page and update, it goes through fine, but when I leave it the same (maybe just changing the staff name for example) I receive this exception.
I tried implementing the various suggestions on this
similar but apparently different SO discussion
and all sorts of ways of tinkering with my repository update method, and strangely enough this is the only way I have got it working:
public async Task UpdateStaff(StaffDto staff)
{
var staffToUpdate = await _db.Staff
.Include(d => d.Departments)
.ThenInclude(d => d.Department)
.SingleOrDefaultAsync(s => s.Id == staff.Id);
// Without either these two line I get the exception
_db.StaffDepartment.RemoveRange(staffToUpdate.Departments);
await _db.SaveChangesAsync();
_mapper.Map(staff, staffToUpdate);
await _db.SaveChangesAsync();
}
So it seems strange that I should have to call SaveChangesAsync() twice in same method. What is actually happening there? And how would I achieve the suggestion in the error When attaching existing entities, ensure that only one entity instance with a given key value is attached without doing so.
Entity classes look like so:
public class Staff
{
public int Id { get; set; }
...
public ICollection<StaffDepartment> Departments { get; set; }
}
public class Department
{
public int DepartmentId { get; set; }
...
public ICollection<StaffDepartment> Staff { get; set; }
}
public class StaffDepartment
{
public int StaffId { get; set; }
public Staff Staff { get; set; }
public int DepartmentId { get; set; }
public Department Department { get; set; }
}
And the staffDto that comes back from the domain etc. is simply:
public class StaffDto
{
public int Id { get; set; }
...
public List<DepartmentDto> Departments { get; set; }
}
The repositories are all registered as transient i.e.
services.AddTransient<ICPDRepository, CPDRepository>();
I have two model
1)
public class Indicator
{
public long ID { get; set; }
public string Name { get; set; }
public int MaxPoint { get; set; }
public string Comment { get; set; }
public DateTime DateChanged { get; set; }
public DateTime DateCreated { get; set; }
public virtual IList<CalculationType> CalculationTypes { get; set; }
public virtual IList<TestEntity> TestEntitys { get; set; }
public virtual IndicatorGroup IndicatorGroup { get; set; }
}
2)
public class CalculationType
{
public long ID { get; set; }
public string UnitName { get; set; }
public int Point { get; set; }
public DateTime DateCreated { get; set; }
public DateTime DateChanged { get; set; }
public virtual Indicator Indicator { get; set; }
public virtual IList<Сalculation> Calculations { get; set; }
}
I executing this code
var indicator = DataContext.Indicators.FirstOrDefault(i => i.ID == indicatorID);
var test = DataContext.CalculationTypes.FirstOrDefault();
first line return null on navigation property CalculationTypes
Second line return empty collection. Why?
UPDATE
snapshot database
project link https://github.com/wkololo4ever/Stankin
added Calculation
public class Сalculation
{
public long ID { get; set; }
public virtual CalculationType CalculationType { get; set; }
public virtual ApplicationUser Creator { get; set; }
}
1) Is Lazy Loading enabled? If not, you need to explicitly load your navigation properties with the '.Include' syntax.
2) Are you sure EF should be able to detect that relation? Did you use Code First or Database First?
Edit: 3) Are you sure there is data in your database and that the foreign key from Indicator to IndicatorGroup has a value for that specific record? I am saying this because the value "null" is valid if there is simply no data.
P.S. If you do not see a foreign key on Indicator called "IndicatorGroupId", there might be an "IndicatorId" on the table "IndicatorGroup", in which case - going from the names you provided - your database is misconfigured and you will need to use fluent syntax or data attributes to instruct EF on how to make the foreign keys.
Try this:
DbContext.Configuration.ProxyCreationEnabled = true;
DbContext.Configuration.LazyLoadingEnabled = true;
If DbContext.Configuration.ProxyCreationEnabled is set to false, DbContext will not load child objects for some parent object unless Include method is called on parent object. Setting DbContext.Configuration.LazyLoadingEnabled to true or false will have no impact on its behaviours.
If DbContext.Configuration.ProxyCreationEnabled is set to true, child objects will be loaded automatically, and DbContext.Configuration.LazyLoadingEnabled value will control when child objects are loaded.
I think this is problem:
Edit: 3) Are you sure there is data in your database and that the
foreign key from Indicator to IndicatorGroup has a value for that
specific record? I am saying this because the value "null" is valid if
there is simply no data.
P.S. If you do not see a foreign key on Indicator called
"IndicatorGroupId", there might be an "IndicatorId" on the table
"IndicatorGroup", in which case - going from the names you provided -
your database is misconfigured and you will need to use fluent syntax
or data attributes to instruct EF on how to make the foreign keys.
Try to this and make sure foreign key is corrected.
public class CalculationType
{
public long ID { get; set; }
public string UnitName { get; set; }
public int Point { get; set; }
public DateTime DateCreated { get; set; }
public DateTime DateChanged { get; set; }
[ForeignKey("IndicatorID")]
public string IndicatorId { get; set; } //this is the foreign key, i saw in your database is: Indicator_ID, avoid this, rename it to IndicatorID or IndicatorId
public virtual Indicator Indicator { get; set; }
public virtual IList<Сalculation> Calculations { get; set; }
}
Same behavior, but different root cause than selected answer:
Navigation property can also be null if you turned off myContext.Configuration.AutoDetectChangesEnabled
Very obvious, but this got me when I was implementing some performance improvments.
Check this out: Navigation Property With Code First . It mentions about why navigation property is null and the solutions of it.
By default, navigation properties are null, they are not loaded by
default. For loading navigation property, we use “include” method of
IQuearable and this type of loading is called Eager loading.
Eager loading: It is a process by which a query for one type of entity
loads the related entities as a part of query and it is achieved by
“include” method of IQueryable.
I experienced this issue, where navigation properites were not loaded, even when the Include statement was present.
The problem was caused by string-comparison differences between SQL Server and EF6 using .NET. I was using a VARCHAR(50) field as the primary key in my customers table and also, as a foreign key field in my audit_issues table. What I did not realize was that my keys in the customers table had two additional white space characters on the end; these characters were not present in my audit_issues table.
However, SQL Server will automatically pad whitespace for string comparisons. This applies for WHERE and JOIN clauses, as well as for checks on FOREIGN KEY constraints. I.e. the database was telling me string were equivalent and the constraint passed. Therefore I assumed that they actually were exactly equal. But that was false. DATALENGTH of one field = 10, while the DATALENGTH of the other = 8.
EF6 would correctly compose the SQL query to pull the foreign key related fields and I would see them both in the generated Sql query and in the results. However, EF6 would silently fail when loading the Navigation Properties because .NET does not consider those strings equal. Watch out for whitespace in string-type foreign key fields!.
This article helped me.
In sum :
Install-Package Microsoft.EntityFrameworkCore.Proxies
In Startup.cs
using Microsoft.EntityFrameworkCore;
public void ConfigureServices(IServiceCollection services)
{
...
services.AddDbContext<MyDbContext>(builder =>
{
builder.UseLazyLoadingProxies(); // <-- add this
}, ServiceLifetime.Singleton);
This is a variant of Keytrap's answer. Using .NET 6 and EF Core 6, I created a ContextPartials.cs for any custom configurations that I don't want EF's Scaffold command to overwrite:
Required Package:
Install-Package Microsoft.EntityFrameworkCore.Proxies
Code (ContextPartials.cs):
// NOTE: I am not using the new file-scoped namespace on purpose
namespace DataAccess.Models.MyDatabase
{
// NOTE: This is a partial outside of the generated file from Scaffold-DbContext
public partial class MyDatabaseContext
{
// NOTE: This enables foreign key tables to become accessible
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.UseLazyLoadingProxies();
}
}
My web application is built with MVC and Entity Framework Code First. To explain my question, I'm describing it with a simplified Album - Song example:
public abstract class MusicStoreEntity
{
[Display(AutoGenerateField = false), Key, Required, Column(Order = 0)]
public Guid Id { get; set; }
}
[Table(TableName), DataContract]
public class Album : MusicStoreEntity
{
public const string TableName = "Albums";
public virtual Collection<AlbumSong> Songs { get; set; }
}
[Table(TableName), DataContract]
public class Song : MusicStoreEntity
{
public const string TableName = "Songs";
public virtual Collection<AlbumSong> Albums { get; set; }
}
The two entities are connected many-to-many with a separate entity that contains two foreign keys, as well as an unique identifier:
[Table(TableName), DataContract]
public class AlbumSong : MusicStoreEntity
{
public const string TableName = "AlbumSongs";
[DataMember, Required]
public Guid AlbumId{ get; set; }
public virtual Album Album { get; set; }
[DataMember, Required]
public Guid SongId { get; set; }
public virtual Song Song { get; set; }
}
With an incoming API call, you can create a new entity if the two entities aren't connected yet:
public void SetAlbumSong(Guid albumId, Guid songId) {
var albumSong = DBContext.Set<AlbumSong>().SingleOrDefault(a => a.AlbumId == albumId && a.SongId == songId);
if(albumSong == null) {
var albumSong = new AlbumSong {
AlbumId = albumId,
SongId = songId
}
DBContext.Set<AlbumSong>().Add(albumSong);
DBContext.SaveChanges();
} else {
// update existing albumSong
}
}
But, when two API calls come in at approximately the same time with the same entity id's, there is a window that enables adding two AlbumSong entities between the same album and song.
One solution is making a Composite Key that consists of both foreign id's in the AlbumSong entity. This way no two duplicate AlbumSongs can be made and an exception will be thrown.
A similar solution is adding an extra property to AlbumSong that combines the two id's and requiring the column to be unique.
However, I wondered if there are other (better, cleaner) solutions to this problem, as above solutions bring unwanted changes for my specific application.
(My web application is built with MVC 4 and Entity Framework 5.)
A composite key is a good idea, and can be viewed as better database design, because the AlbumID and SongID are what truly identify the record as unique.
EF provides concurrency checking, and wouldn't require many changes other than updating your model and adding a try/catch to your else statement. This article can take you through the process, but will add a field to your database. This article uses concurrency checking, but does not add a field to the database.
I am using EntityFramework for the first time and maybe this question is so simple...I've used code first method..I have a Class Personnel which looks like this:
public class Personnel
{
public string Id { set; get; }
public int Code { set; get; }
public string Name { set; get; }
public int Type { set; get; }
public JobTitle Title { set; get; }
}
and the JobTitle class:
public class JobTitle
{
public string Id { set; get; }
public int Number { set; get; }
public string Title { set; get; }
public List<Personnel> Personnels { set; get; }
}
which the last property in Personnel Class is a foreign key in personnel table of course..my problem is when I want to retrieve all personnels ( or a personnel ) from DB using lambda expression..the foreign key object is null..the lambda expression is like below:
Context.ContextInstance.Personnels.ToList();
and if I change the expression to this the foreign key object is not null any more.
Context.ContextInstance.Personnels.Include("Title").ToList();
is it the right way??..is there any better way??..I supposed that EF will automatically understand that!!!!..if there are more than 1 FK then I have to use Include for all of them?? please help me to understand.
Thanks
This is due to lazy loading. When you call Context.ContextInstance.Personnels.ToList(); this will fetch all personnel's but Title will not fetch until it get instanced, so make it virtual to get it.
or, you can disable lazy loading by
public MyEntitiesContext() : base("name=MyEntitiesContext", "MyEntitiesContext") {
this.Configuration.LazyLoadingEnabled = false;
}
Doing this will get all related data from context. Using "include" is loading on demand, when you specify properties you want to query.
Virtual keyword allows entity framework runtime create dynamic proxies for your entity classes and their properties, and by that support lazy loading. Without virtual, lazy loading will not be supported, and you get null on collection properties.
If your JobTitle property would be defined as virtual, you wouldn't need to use include.
It's really good explained here: Entity Framework 4.1 Virtual Properties