SQLite EF6 Async API throws InvalidCastException - c#

I'm new to entity framework and try to use it with SQLite. I've a working setup if I don't use the async API.
In my simplified szenario are only two tables containing a one-to-many relationship. DB setup and insertion of values works fine. Query is a problem.
Here is the simplified code:
var connectionStringBuilder = new SQLiteConnectionStringBuilder { DataSource = dbFile, ForeignKeys = true };
using (var connection = new SQLiteConnection { ConnectionString = connectionStringBuilder.ConnectionString })
{
await connection.OpenAsync();
// query data
using (var context = new TestContext(connection, contextOwnsConnection: false))
{
var xQuery = context.Xs.Include(item => item.Ys);
var syncX = xQuery.First(); // works fine
var asyncX = await xQuery.FirstAsync(); // throws InvalidCastException
}
}
Why does the call to FirstAsync() throws an exception while First() doesn't?
The same happens using SingleAsync() and ToListAsync(). Currently I'm of the opinion that this happens on all async calls.
Removing the Include(...) clause from query will fix the problem but forces a second database query on access to the property.
To avoid hints like "you're calling First and FirstAsync on the same query object...": NO. The problem still occurs if I only call ...Async() without calling a synchronous method first. That's just for clearification.
I'm using a WinForms application .Net 4.7.1, EF6 (by adding System.Data.SQLite v1.0.108 via Nuget).
The complete code to reproduce the problem:
private async void OnClick(object sender, EventArgs e)
{
var dbFile = "test.sqlite";
if (File.Exists(dbFile)) File.Delete(dbFile);
SQLiteConnection.CreateFile(dbFile);
var connectionStringBuilder = new SQLiteConnectionStringBuilder { DataSource = dbFile, ForeignKeys = true };
using (var connection = new SQLiteConnection { ConnectionString = connectionStringBuilder.ConnectionString })
{
await connection.OpenAsync();
// setup database
using (var context = new TestContext(connection, contextOwnsConnection: false))
{
await context.Database.ExecuteSqlCommandAsync("CREATE TABLE X (Id VARCHAR2 PRIMARY KEY);");
await context.Database.ExecuteSqlCommandAsync("CREATE TABLE Y (Id VARCHAR2 PRIMARY KEY, XId VARCHAR2 NOT NULL, FOREIGN KEY (XId) REFERENCES X(Id));");
var x0 = new X { Id = "X0" };
var y0 = new Y { Id = "Y0", XId = x0.Id }; // Currently I don't know why using the navigation property 'X = x0' isn't working but the XId will work.
var y1 = new Y { Id = "Y1", XId = x0.Id }; // Currently I don't know why using the navigation property 'X = x0' isn't working but the XId will work.
x0.Ys.Add(y0);
x0.Ys.Add(y1);
context.Xs.Add(x0);
context.Ys.Add(y0); // not needed but for safety :-)
context.Ys.Add(y1); // not needed but for safety :-)
await context.SaveChangesAsync();
}
// query data
using (var context = new TestContext(connection, contextOwnsConnection: false))
{
var xQuery = context.Xs.Include(item => item.Ys);
var syncX = xQuery.First(); // works fine
var asyncX = await xQuery.FirstAsync(); // throws InvalidCastException
}
}
}
Using the following model classes:
public class TestContext : DbContext
{
public TestContext(DbConnection existingConnection, bool contextOwnsConnection = true) :
base(existingConnection, contextOwnsConnection)
{
}
public DbSet<X> Xs { get; set; }
public DbSet<Y> Ys { get; set; }
}
[Table("X")]
public class X
{
public X()
{
// ReSharper disable once VirtualMemberCallInConstructor
this.Ys = new HashSet<Y>();
}
[Column("Id")]
[Key, Required]
public string Id { get; set; }
public virtual ICollection<Y> Ys { get; set; }
}
[Table("Y")]
public class Y
{
[Column("Id")]
[Key, Required]
public string Id { get; set; }
[Column("XId")]
[Required]
public string XId { get; set; }
[ForeignKey("XId")]
public virtual X X { get; set; }
}

I'm sorry, I can't write comments.
Your problem is very interesting, the first thought I had was that the types do not match, because I remembered exactly what FirstAsync takes IQueryable, and if I do include something the type of request is IIncludeableQueryable, but I tested my guess and looked at the implementation through dot-peek, alas, it work in both cases, and by itself IIncludeableQueryableis inherited from IQueryable
var testQuery = DbContext.Practices.Include(x => x.Facility);
var testQuery2 = DbContext.Practices.Select(x => x);
var asyncPracticeCorrectType = await testQuery2.FirstAsync();
var asyncPractice = await testQuery.FirstAsync();
i got entity Practice in both ways:(
I think your problem in area SQLite, because your code look correctly

I tried entity framework core instead of entity framework 6 which works perfectly according to that problem.
In my option it's a bug in entitiy framework 6 or in the sqlite provider.

Related

Why is EF inserting new data for entities that I'm not specifying?

I'm going to chunk this down to as simple a case as I can, but this happens for everything.
I'm basing most of my data model POCO objects on a BaseDataObject defined as follows:
public class BaseDataObject
{
public int Id { get; set; }
public bool Deleted { get; set; }
}
My code-first data model has a Client object:
public class Client : BaseDataObject
{
public string Name { get; set; }
public virtual Category Category { get; set; }
public virtual Category Subcategory { get; set; }
}
The Category object is pretty simple:
public class Category : BaseDataObject
{
public string Name { get; set; }
}
The required Id property exists in the inherited BaseDataObject.
To add entities, I'm using the following repo:
public class DataRepository<TModel, TContext>
where TModel : BaseDataObject
where TContext : DbContext
{
public int AddItem(T item)
{
using (var db = (TContext)Activator.CreateInstance(typeof(TContext)))
{
db.Set<T>().Add(item);
db.SaveChanges();
}
}
// These are important as well.
public List<T> ListItems(int pageNumber = 0)
{
using (var db = (TContext)Activator.CreateInstance(typeof(TContext)))
{
// Deleted property is also included in BaseDataObject.
return db.Set<T>().Where(x => !x.Deleted).OrderBy(x => x.Id).Skip(10 * pageNumber).ToList();
}
public T GetSingleItem(int id)
{
using (var db = (TContext)Activator.CreateInstance(typeof(TContext)))
{
return db.Set<T>().SingleOrDefault(x => x.Id == id && !x.Deleted);
}
}
}
This adds a new client perfectly fine, but there's something weird about my data model here that's causing Entity Framework to also add 2 new Categories every time I add a client based on which categories I'm selecting on my form.
Here's my form's code:
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
try
{
BindDropDownList<Category>(CategoryList);
BindDropDownList<Category>(SubcategoryList);
}
// Error handling things
}
}
private void BindDropDownList<TModel>(DropDownList control) where TModel : BaseDataObject
{
var repo = new DataRepository<TModel, ApplicationDbContext>();
control.DataSource = repo.ListItems();
control.DataTextField = "Name";
control.DataValueField = "Id";
control.DataBind();
control.Items.Insert(0, new ListItem("-- Please select --", "0"));
}
private TModel GetDropDownListSelection<TModel>(DropDownList control) where TModel : BaseDataObject
{
var repo = new DataRepository<TModel, ApplicationDbContext>();
int.TryParse(control.SelectedItem.Value, out int selectedItemId);
return repo.GetSingleItem(selectedItemId);
}
protected void SaveButton_Click(object sender, EventArgs e)
{
try
{
var repo = new DataRepository<Client, ApplicationDbContext();
var selectedCategory = GetDropDownListSelection<Category>(CategoryList);
var selectedSubcategory = GetDropDownListSelection<Category>(SubcategoryList);
var name = NameTextBox.Text;
var client = new Client
{
Name = name,
Category = selectedCategory,
Subcategory = selectedSubcategory
};
repo.AddItem(client);
}
// Error handling things
}
Unless there's something wrong with the way I'm creating the relationship here (using the virtual keyword or something maybe) then I can't see any reason why this would add new Categories to the database as duplicates of existing ones based on the selections I make in the drop down lists.
Why is this happening? What have I got wrong here?
The DbSet<T>.Add method cascades recursively to navigation properties which are not currently tracked by the context and marks them as Added. So when you do
db.Set<T>().Add(item);
it actually marks both Client class referenced Category entities as Added, hence SaveChanges inserts two new duplicate Category records.
The usual solution is to tell EF that entities are existing by attaching them to the context in advance. For instance, if you replace repo.AddItem(client); with
using (var db = new ApplicationDbContext())
{
if (client.Category != null) db.Set<Category>().Attach(client.Category);
if (client.Subcategory != null) db.Set<Category>().Attach(client.Subcategory);
db.Set<Client>().Add(item);
db.SaveChanges();
}
everything will be fine.
The problem is that you use generic repository implementation which does not provide you the necessary control. But that's your design decision issue, not EF. The above is EF intended way to handle such operation. How you can fit it into your design is up to you (I personally would eliminate the generic repository anti-pattern and use directly the db context).
It is really hard to judge from your listing because no FK mappings are included nor the base model details are provided.
However, it would appear that the Category that you assigned to client does not have PK set, and (most likely) only has the Name set, and you have no unique IX on that.
So EF has no reasonable way to work out that this is the right category.
One way to sort it is
protected void SaveButton_Click(object sender, EventArgs e)
{
try
{
var repo = new DataRepository<Client, ApplicationDbContext>();
var selectedCategory = GetDropDownListSelection<Category>(CategoryList);
var selectedSubcategory = GetDropDownListSelection<Category>(SubcategoryList);
var name = NameTextBox.Text;
var client = new Client
{
Name = name,
// either
Category = new DataRepository<Category , ApplicationDbContext>().GetSingleItem(selectedCategory.id),
// or, easier (assuming you have FK properties defined on the model)
CategoryId = selectedCategory.Id,
// repeat as needed
Subcategory = selectedSubcategory
};
repo.AddItem(client);
}
// Error handling things
}

How can I use the Entity Framework Core capabilities to change my database?

I am building an application on top of Entity Framework Core and I want to, sort of, apply a migration at runtime.
My intended approach is to have the current database model in memory and create a new model, then calculate the difference between the two models using IMigrationsModelDiffer.GetDifferences().
From there, instead of printing the differences into a Migration class, I want to create the MigrationCommands directly and apply those commands to my database.
The above sounds fairly straightforward but I'm having a lot of issues with the Dependency Injection system.
This is the code I have right now:
static DbContextOptions GetOptions(IModel model)
{
var builder = new DbContextOptionsBuilder();
builder
.UseSqlServer(connStr)
.UseModel(model);
return builder.Options;
}
class Test1ModelAEntity
{
public int Id { get; set; }
public string StrProp { get; set; }
}
static void Main(string[] args)
{
var sqlServerServices = new ServiceCollection()
.AddEntityFrameworkSqlServer()
.BuildServiceProvider();
var conventions = new ConventionSet();
sqlServerServices.GetRequiredService<IConventionSetBuilder>().AddConventions(conventions);
var emptyModelBuilder = new ModelBuilder(conventions);
var emptyModel = emptyModelBuilder.Model;
var test1ModelBuilder = new ModelBuilder(conventions);
test1ModelBuilder.Entity<Test1ModelAEntity>()
.ToTable("ModelA");
var test1Model = test1ModelBuilder.Model;
using (TestContext ctx = new TestContext(GetOptions(emptyModel)))
{
var migrationServices = new ServiceCollection()
.AddDbContextDesignTimeServices(ctx)
.AddEntityFrameworkSqlServer()
.BuildServiceProvider();
var operations = migrationServices.GetRequiredService<IMigrationsModelDiffer>().GetDifferences(emptyModel, test1Model);
var commands = migrationServices.GetRequiredService<IMigrationsSqlGenerator>().Generate(operations, test1Model);
var connection = migrationServices.GetRequiredService<IRelationalConnection>();
migrationServices.GetRequiredService<IMigrationCommandExecutor>().ExecuteNonQuery(commands, connection);
}
}
This code throws a NullReferenceException with this stack trace:
at Microsoft.EntityFrameworkCore.Metadata.Internal.TableMapping.<>c.<GetRootType>b__10_0(IEntityType t)
at System.Linq.Enumerable.SingleOrDefault[TSource](IEnumerable`1 source, Func`2 predicate)
at Microsoft.EntityFrameworkCore.Migrations.Internal.MigrationsModelDiffer.GetSortedProperties(TableMapping target)
at Microsoft.EntityFrameworkCore.Migrations.Internal.MigrationsModelDiffer.<Add>d__37.MoveNext()
at Microsoft.EntityFrameworkCore.Migrations.Internal.MigrationsModelDiffer.<DiffCollection>d__73`1.MoveNext()
at System.Linq.Enumerable.ConcatIterator`1.MoveNext()
at Microsoft.EntityFrameworkCore.Migrations.Internal.MigrationsModelDiffer.Sort(IEnumerable`1 operations, DiffContext diffContext)
at Sandbox.Program.Main(String[] args) in D:\Sandbox\Program.cs:line 108
I have inspected the source code and it appears that there's an issue with the way EFCore is interpreting my model. I am using EFCore version 2.1 preview 2.
Really I'm mostly trying random configurations on my IServiceCollections because I have no idea how to set this up. I am also trying to stay away from EFCore internal classes but if needed be I may use one or two for the time being.
Is there a way to take advantage of EFCore's built-in capabilities to generate some SQL given a pair of IModels? If so, how do I set up DI to have all the required services?
Thank you for the comments which pointed me in the correct direction.
In summary, I was trying to create my models using an empty convention set. This obviously leads to all sorts of problems as you have to generate the entire model explicitly, which is very complex.
To use the expected convention set I had to get it from my context using ConventionSet.CreateConventionSet. I also had to manually validate my model before being able to use it in queries and insert commands. The rest of the logic is pretty much the same.
Here's my final code including the tests I ran to ensure everything worked as expected:
static DbContextOptions GetOptions(IModel model)
{
var builder = new DbContextOptionsBuilder();
builder
.UseSqlServer(connStr)
.UseModel(model);
return builder.Options;
}
//Test 1
class Test1EntityA
{
public int Id { get; set; }
public string StrProp { get; set; }
}
//Test 2
class Test2EntityA
{
public int Id { get; set; }
public string StrProp { get; set; }
public ICollection<Test2ModelBEntity> Children { get; set; }
}
class Test2EntityB
{
public int Id { get; set; }
public int EntityAId { get; set; }
public Test2EntityA EntityA { get; set; }
}
static void Main(string[] args)
{
var emptyModelBuilder = new ModelBuilder(new ConventionSet());
var emptyModel = emptyModelBuilder.Model;
using (var baseCtx = new TestContext(GetOptions(emptyModel)))
{
//Get all services we need from the base context
var conventions = ConventionSet.CreateConventionSet(baseCtx);
var migrationServices = new ServiceCollection()
.AddDbContextDesignTimeServices(baseCtx)
.AddEntityFrameworkSqlServer()
.BuildServiceProvider();
//Test 1
var test1ModelBuilder = new ModelBuilder(conventions);
test1ModelBuilder.Entity<Test1EntityA>()
.ToTable("EntityA");
var test1Model = test1ModelBuilder.GetInfrastructure().Metadata;
test1Model.Validate();
var operations = migrationServices.GetRequiredService<IMigrationsModelDiffer>().GetDifferences(emptyModel, test1Model);
var commands = migrationServices.GetRequiredService<IMigrationsSqlGenerator>().Generate(operations, test1Model);
var connection = migrationServices.GetRequiredService<IRelationalConnection>();
migrationServices.GetRequiredService<IMigrationCommandExecutor>().ExecuteNonQuery(commands, connection);
using (TestContext ctx = new TestContext(GetOptions(test1Model)))
{
ctx.Set<Test1EntityA>().Add(new Test1EntityA
{
StrProp = "test1",
});
ctx.SaveChanges();
}
//Test 2
var test2ModelBuilder = new ModelBuilder(conventions);
test2ModelBuilder.Entity<Test2EntityA>()
.ToTable("EntityA");
test2ModelBuilder.Entity<Test2EntityB>()
.ToTable("EntityB");
var test2Model = test2ModelBuilder.GetInfrastructure().Metadata;
test2Model.Validate();
operations = migrationServices.GetRequiredService<IMigrationsModelDiffer>().GetDifferences(test1Model, test2Model);
commands = migrationServices.GetRequiredService<IMigrationsSqlGenerator>().Generate(operations, test2Model);
migrationServices.GetRequiredService<IMigrationCommandExecutor>().ExecuteNonQuery(commands, connection);
using (TestContext ctx = new TestContext(GetOptions(test2Model)))
{
var e = new Test2EntityA
{
StrProp = "test2",
};
ctx.Set<Test2EntityA>().Add(e);
ctx.Set<Test2EntityB>().Add(new Test2EntityB
{
EntityA = e,
});
ctx.SaveChanges();
Console.WriteLine(ctx.Set<Test2EntityB>().Where(b => b.EntityA.StrProp == "test2").Count());
}
}
}

How to clone object with a different Primary Key

I have this class Cart_Record, shown below. I want to update the PrimaryKey. To do that I am trying to clone the object into a new object to copy CartLines and update ID. I haven't found much in the issue queue or the documentation to help me.
public class Cart_Record : RealmObject
{
public Cart_Record() { }
public Cart_Record(IList<Cart_Line> cartLines, int id)
{
ID = id;
foreach (var cartLine in cartLines)
CartLines.Add(App.RealmDB.Find<Cart_Line>(cartLine.ProductId));
}
[PrimaryKey]
public int ID { get; set; }
public IList<Cart_Line> CartLines { get; }
}
I am trying this
var appCart = App.RealmDB.All<Cart_Record>().First();
App.RealmDB.Write(() =>
{
var cartLines = new List<Cart_Line>(appCart.CartLines);
App.RealmDB.Remove(App.RealmDB.Find<Cart_Record>(appCart.ID));
App.RealmDB.Add<Cart_Record>(new Cart_Record(cartLines, serverCart.ID));
});
However I keep getting exceptions, specifically RealmObjectManagedByAnotherRealmException. I don't understand how as I am not readding the Cart_Line objects to Realm, just to the CartLine list in the new object.
What am I doing wrong?
Thanks ahead of time.
Edit: I found something that works but I would like to see if someone else have a better method. This is what works for me.
var appCart = App.RealmDB.All<Cart_Record>().First();
App.RealmDB.Write(() =>
{
var cartLines = new List<Cart_Line>(appCart.CartLines);
App.RealmDB.Remove(App.RealmDB.Find<Cart_Record>(appCart.ID));
var newAppCart = App.RealmDB.Add<Cart_Record>(new Cart_Record() { ID = serverCart.ID });
foreach (var cartLine in cartLines)
newAppCart.CartLines.Add(cartLine);
});
I'm not sure what App.RealmDB does under the hood, but using the out-of-the-box Realm API, what you want to achieve can be done by simply adding the CartLines from the original to the updated object:
// Assume want to change Id from 1 to 2
var realm = Realm.GetInstance();
var original = realm.Find<Cart_Record>(1);
var updated = new Cart_Record { ID = 2 }; // other properties must be copied here
foreach (var cart in original.CartLines)
{
updated.CartLines.Add(cart);
}
realm.Write(() =>
{
realm.Remove(original);
realm.Add(updated);
});
// updated now has all the original's CartLines

MongoDB & Web API - can't understand how to use filters

I am trying to do basic CRUD on a MongoDB using C# Web API. All the examples online are for deprecated methods and I simply can't get the new methods to work.
the places where i need help in the code bellow:
Delete all items in collection - syntax to remove all.
get all items in collection - filter for get all
get by id = syntax to select by id
i've tried many examples online. most are deprecated for previous versions of MongoDB and the official Mongo docs are not helping, i suspect as my code is involving WebAPI classes and the examples are not for that.
thanks for your help!
This is the class:
public class template
{
[BsonId]
public string templateUniqueId { get; set; }
public string outsideClientId { get; set; }
public string ClientId { get; set; }
public string templateFieldsData { get; set; }
public bool isActive { get; set; }
}
And my repository implementation (part of if):
public class templateRepository : ItemplateRepository
{
public MongoClient client;
public IMongoDatabase database;
public IMongoCollection<template> templatesCollection;
public templateRepository()
{
string connectionString = ConfigurationManager.AppSettings["mongoconnection"];
if (string.IsNullOrWhiteSpace(connectionString))
{
connectionString = "mongodb://localhost:27017";
}
client = new MongoClient(connectionString);
database = client.GetDatabase(ConfigurationManager.AppSettings["mongo_personal"]);
templatesCollection = database.GetCollection<template>("templates");
//var persons = await collection.Find(new BsonDocument()).ToListAsync();
// Reset database and add some default entries
FilterDefinition<template> f;
var result = templatesCollection.DeleteMany(f); // this line should remove all items
for (int index = 1; index < 5; index++)
{
template template1 = new template
{
templateUniqueId = string.Format("t{0}", index.ToString()),
outsideClientId= string.Format("t{0}", index.ToString()),
ClientId = string.Format("t{0}", index.ToString()),
isActive = true,
templateFieldsData = "sharon"
};
AddTemplate(template1);
}
}
public void AddTemplate(template templateIn)
{
database.GetCollection<template>("templates").InsertOne(templateIn);
}
public IEnumerable<template> GetAllTemplates()
{
var templates = database.GetCollection<template>("templates").Find({ }); // get all templates
return templates;
}
public template GetTemplate(string id)
{
IMongoQuery query = Query.EQ("_id", id);
return templatesCollection.Find(query).FirstOrDefault();
}
public bool UpdateTemplate(string id, template item)
{
IMongoQuery query = Query.EQ("_id", id);
item.LastModified = DateTime.UtcNow;
IMongoUpdate update = Update
.Set("outsideClientId", item.outsideClientId)
.Set("ClientId", item.ClientId)
.Set("isActive", item.isActive)
.Set("templateFieldsData", item.templateFieldsData);
SafeModeResult result = templatesCollection.Update(query, update);
return result.UpdatedExisting;
}
}
You are using the legacy driver methods, here are some examples for MongoDB C# Driver 2.2:
Delete all items in collection, syntax to remove all.
coll.DeleteMany(FilterDefinition<template>.Empty);
Get all items in collection, filter for get all
var results = coll.Find(FilterDefinition<template>.Empty).ToList();
Get by id, syntax to select by id
var filter = Builders<template>.Filter.Eq(t=>t.templateUniqueId, id);
var result = coll.Find(filter).FirstOrDefault();
Also, for your update method, you can use the following syntax:
var filter = Builders<template>.Filter.Eq(t=>t.templateUniqueId, id);
var update = Builders<template>.Update
.Set(t=>t.outsideClientId, item.outsideClientId)
.Set(t=>t.ClientId, item.ClientId)
.Set(t=>t.isActive, item.isActive)
.Set(t=>t.templateFieldsData, item.templateFieldsData);
var updateResult = coll.UpdateOne(filter, update);
return result.ModifiedCount > 0;
More information about Definitions and Builders:
http://mongodb.github.io/mongo-csharp-driver/2.0/reference/driver/definitions/

Ensure that explicitly set primary key values are unique

I am getting following exception on my project:
An exception of type 'System.InvalidOperationException' occurred in
EntityFramework.dll but was not handled in user code
Additional information: Saving or accepting changes failed because of
more than one entity of type 'MyProject.Data.Poco.MyProjectCountry' 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. Use the
Entity Designer for Database First/Model First configuration. Use the
'HasDatabaseGeneratedOption" fluent API or
'DatabaseGeneratedAttribute' for Code First configuration.
the error happens at the following line:
using (MyProjectDataContext context = new MyProjectDataContext())
{
MyProjectItemTag existingItemTag = (from p in context.ItemTags.Include(p => p.MyProjectGenre).Include(p => p.MyProjectCountry)
where p.MyProjectUser.UserId == ItemTag.MyProjectUser.UserId &&
p.MyProjectItem.ItemId == MyProjectItem.ItemId
select p).FirstOrDefault();
// new tag
if (existingItemTag == null)
{
existingItemTag = ItemTag;
existingItemTag.MyProjectItem.ItemId = MyProjectItem.ItemId;
}
// existing tag
else
{
existingItemTag.MyProjectItem = new MyProjectItem { ItemId = MyProjectItem.ItemId };
existingItemTag.MyProjectUser = new MyProjectUser { UserId = ItemTag.MyProjectUser.UserId };
}
// updates
existingItemTag.MyProjectCountry = MyProjectCountry;
if (MyProjectCountry != null)
existingItemTag.MyProjectCountry = new MyProjectCountry()
{
MyProjectCountryId = MyProjectCountry.MyProjectCountryId
};
existingItemTag.MyProjectGenre = MyProjectGenre;
context.Entry(existingItemTag.MyProjectItem).State = EntityState.Unchanged;
context.Entry(existingItemTag.MyProjectUser).State = EntityState.Unchanged;
context.Entry(existingItemTag.MyProjectCountry).State = EntityState.Unchanged;
context.Entry(existingItemTag.MyProjectGenre).State = EntityState.Unchanged;
if (existingItemTag.MyProjectCountry != null)
{
context.Entry(existingItemTag.MyProjectCountry).State = EntityState.Unchanged;
}
// db
context.ItemTags.AddOrUpdate(existingItemTag);
context.SaveChanges();
return existingItemTag.ItemTagId;
}
context.Entry(existingItemTag.MyProjectCountry).State = EntityState.Unchanged;
My Class:
public class MyProjectItemTag
{
public int ItemTagId { get; set; }
public MyProjectUser MyProjectUser { get; set; }
public MyProjectItem MyProjectItem { get; set; }
public MyProjectCountry MyProjectCountry { get; set; }
public MyProjectGenre MyProjectGenre { get; set; }
public MyProjectMood MyProjectMood { get; set; }
public MyProjectItemTag()
{
}
public MyProjectItemTag(string userId, string providerContentId)
{
MyProjectUser = new MyProjectUser
{
UserId = userId
};
MyProjectItem = new MyProjectItem
{
ProviderContentId = providerContentId
};
}
}
My Config:
public class MyProjectItemTagConfiguration : EntityTypeConfiguration<MyProjectItemTag>
{
public MyProjectItemTagConfiguration()
{
ToTable("MyProjectItemTags");
HasKey(p => p.ItemTagId);
HasRequired(p => p.MyProjectUser);
HasRequired(p => p.MyProjectItem);
HasOptional(p => p.MyProjectCountry);
}
}
What I am missing here?
This is all you really need to look for:
Additional information: Saving or accepting changes failed because more than one entity of type 'MyProject.Data.Poco.MyProjectCountry' have the same primary key value.
The following code may not necessarily populate the MyProjectCountry.
MyProjectItemTag existingItemTag =
(from p in context.ItemTags
.Include(p => p.MyProjectGenre)
.Include(p => p.MyProjectCountry)
where p.MyProjectUser.UserId == ItemTag.MyProjectUser.UserId
&& p.MyProjectItem.ItemId == MyProjectItem.ItemId
select p).FirstOrDefault();
So you set it to some variable you haven't give us any context too...
existingItemTag.MyProjectCountry = MyProjectCountry;
I'd assume it is not null, so you change it's ID which is a Giant Code Smell...
(Why assign it? after all it's already assigned..)
if (MyProjectCountry != null)
existingItemTag.MyProjectCountry = new MyProjectCountry()
{
MyProjectCountryId = MyProjectCountry.MyProjectCountryId
};
Then you tell EF it hasn't changed?? Another Code Smell.
context.Entry(existingItemTag.MyProjectCountry).State = EntityState.Unchanged;
So what this tells me is that the Context has already downloaded this entity into its Object Cache, but the one you are assigning is not the one in the cache so when I tries to added to the cache, there is a duplicate.
Try to use context.Model.AddORUpdate(model) Method, you need to add using System.Data.Entity.Migrations as well for this method.
Check in the.edmx file StoreGeneratedPattern. If the Id in the database is auto generated, StoreGeneratedPattern has to be Identity. In my case was None. It's not the best practice to edit the edmx file. I personally deleted the table in the edmx file, I created a new one and after that the StoreGeneratedPattern = Identity.

Categories