I've been trying to save a complex entity in EF code-first using Json.NET for a couple of days without success.
[Major edit and tl;dr;:] Is there a way to deserialise a JSON object into an entity and keep their relationships?
I can store it the regular way. My problem is after deserialising the object.
By design, the Preferences should added to the database, but their Values are foreign keys (giving a PreferenceValue table).
This is my model (oversimplified for brevity):
public class Preference {
public virtual ICollection<PreferenceAttribute> Attributes { get; set; }
}
public class PreferenceAttribute {
public virtual ICollection<Value> Values { get; set; }
}
public class Value {
public int ValueId { get; set; }
public string Description { get; set; }
}
The values seem not to be attached to the context before saving, causing the engine to store new Values instead of using the foreign keys provided by the JSON object, which looks like:
{
"PreferenceAttributes":[{
"PreferenceTypeId" : 1,
"Values":[
{
"ValueId" : 1
},
{
"ValueId" : 2
},
{
"ValueId" : 3
},
]
}]
}
I can save it whithout any problems directly in C#; the "code" I use to seed the preferences:
var attribute = new PreferenceAttribute {
AttributeId = 1,
Values = context.Values.OrderBy(a => a.ValueId).Skip(1).Take(5).ToList();
};
var preferece = new Preference {
Attributes = new List<PreferenceAttribute> {
attribute
}
};
//user is fetched from "context" as well
user.Preferences.Add(preferece);
context.SaveChanges();
Please, keep in mind that it's just about the Values. The issue is, as noted before, that new Values are being added to the database instead of using their Ids as foreign keys to relate to PrefferenceAttributes, i.e., EF is thinking that I want to add new Values, like so:
attribute.Values = new List<Value> {
new Value {
ValueId = 1, //This id will be ignored by EF since it's not fetched using context; new record will be inserted;
WhateverAttributes = "WTF"
}
}
Best regards.
I think your problem is foreign key records are not referenced by existing record, instead they are newly created and referenced.
I think that issue occurs because all your entities are in Added state. You may want your foreign key records in Unchanged state.
Check this link for entity states http://msdn.microsoft.com/en-us/data/jj592676.aspx
First of all, I would advice you to separate domain model from Dto. Once you do this, you are going to resolve foreign key records manually before you flush them out.
Related
I am using EFCore.BulkExtensions in .NET Core 3.1 with Entity Framework Core.
I have a list containing records that already exist in my database, as well as records that are new and do not yet exist in my database.
I want to only add the new records to my database. I am currently managing to add the new records but I am also unfortunately adding the old ones, meaning duplicates are made in the table.
I have a domain object called InstagramInsightDatum that looks like this and a table in my database to reflect it:
public int InstagramInsightDatumId { get; set; }
public int Value { get; private set; }
public DateTime EndTime { get; private set; }
public int InstagramInsightId { get; set; }
public InstagramInsight InstagramInsight { get; set; }
I want to only add new InstagramInsightDatums to the database when they are unique. I will never need to update these records, just add new ones. At the moment my code looks like this:
I've highlighted the line where I add these datums to the table where the duplicates are happening in red - here you can see I am making the bulkInsert with the InstagramInsightDatums, and setting the PropertiesToExclude to InstagramInsightDatumId (I am not 100% how this config is meant to work but I assumed this feature is to allow us to ignore certain properties when comparing data?) - as removing the PK from the properties should reveal any potential duplicates...
Can anyone see why this isn't working?
A bit old Question, but here's how you would do it: Use UpdateByProperties instead of PropertiesToExclude and set PropertiesToIncludeOnUpdate = new List<string> { "" }. If you do like this, the records with an InstagramInsightDatumId that's not already exists in db will be inserted, and nothing will happen with the ones already existing. Example below:
BulkConfig bulkConfig = new BulkConfig
{
UpdateByProperties = new List<string>() { nameof(InstagramInsightDatum.InstagramInsightDatumId ),
PropertiesToIncludeOnUpdate = new List<string> { "" }
};
_dataContext.BulkInsertOrUpdate(newFormattedDatums, bulkConfig);
You can try: SqlBulkCopyOptions = SqlBulkCopyOptions.CheckConstraints
_dbContext.BulkInsertOrUpdate(entities, new BulkConfig
{
SqlBulkCopyOptions = SqlBulkCopyOptions.CheckConstraints
});
I am trying to seed an user entity in my database. The User entity has an owend property EmailPermissions.
When I run the command
dotnet ef migrations add Initial;
I get the error
The seed entity for entity type 'User' cannot be added because it has the navigation 'EmailPermissions' set. To seed relationships you need to add the related entity seed to 'EmailPermissions' and specify the foreign key values {'UserId'}.
but since EmailPermissions is an owned entity I didn't give it an explicit UserId property, meaning I can't seed it separately in the database.
the entity
public sealed class User : IdentityUser
{
public User()
{
EmailPermissions = new EmailPermissions();
}
/* [..] */
public string TemporaryEmailBeforeChange { get; set; }
public bool IsEmailAwaitingUpdate { get; set; }
public EmailPermissions EmailPermissions { get; set; }
public ICollection<Registeration> Registerations { get; set; }
/* [..] */
}
[Owned]
public class EmailPermissions
{
/* [..] */
public bool Newsletter { get; set; }
public bool PromotionalOffers { get; set; }
public bool PrestationReminders { get; set; }
public bool PrestationOffers { get; set; }
}
The seeding call
private void SeedUser(ModelBuilder builder)
{
builder.Entity<User>().HasData(
new User
{
Id = "37846734-172e-4149-8cec-6f43d1eb3f60",
Email = "foo#foo.foo",
UserName = "foo#foo.foo",
PasswordHash = "AQAAAAEAACcQAAAAEIytBES+jqKH9jfuY3wzKyduDZruyHMGE6P+ODe1pSKM7BuGjd3AIe6RGRHrXidRsg==",
SecurityStamp = "WR6VVAGISJYOZQ3W7LGB53EGNXCWB5MS",
ConcurrencyStamp = "c470e139-5880-4002-8844-ed72ba7b4b80",
EmailConfirmed = true
});
}
If I remove the instantiation of the EmailPermissions property from the constructor I get the following error instead
The entity of type 'User' is sharing the table 'AspNetUsers' with entities of type 'EmailPermissions', but there is no entity of this type with the same key value that has been marked as 'Added'.
How can I seed a user via the .HasData method when it has an owned property ?
Currently this information is missing from the documentation (tracked by #710: Document how to seed owned types). It's explained by EF Core team (with example) in the #12004: Problem seeding data that contains owned type thread:
Owned types must be seeded with a HasData call after the OwnsOne call. Also, since owned types by convention have a primary key generated in shadow state, and since seed data requires keys to be defined, then this requires use of an anonymous type and setting the key.
which is basically what the exception message is telling you.
Following the advice, you should remove the instantiation of the EmailPermissions property from the constructor and add a seeding code like this:
builder.Entity<User>().OwnsOne(e => e.EmailPermissions).HasData(
new
{
UserId = "37846734-172e-4149-8cec-6f43d1eb3f60",
// other properties ...
}
);
Quite annoying and error prone due to the need to know the shadow PK name and the usage of an anonymous type. As the same member mentioned
Note that this would become easier if navigations were supported for seeding, which is tracked by #10000: Data Seeding: Add support for navigations
Thank Ivan Stoev's answer. i add some more code to easy to imagine.
this is code of seed data function base on example.
First adding data of User.
After that add data of owned object.
Data of owned object have to be anonymous because PK will request. This PK will not appear in database. Name should be entity name + Id
Example: Entity XXX => PK will be XXXId
private void SeedUser(ModelBuilder builder)
{
builder.Entity<User>(b =>
{
b.HasData(new User
{
Id = "37846734-172e-4149-8cec-6f43d1eb3f60",
Email = "foo#foo.foo",
UserName = "foo#foo.foo",
// more properties of User
});
b.OwnsOne(e => e.EmailPermissions).HasData(new
{
UserId = "37846734-172e-4149-8cec-6f43d1eb3f60",
Newsletter = true,
PromotionalOffers = true,
PrestationReminders = true,
PrestationOffers = true
});
});
}
If you want to avoid using an anonymous type to specify the shadow property keys, you can declare them explicitly in your model class and configure them with the Fluent API as keys. This way you don't have to guess the property names and it's less error-prone.
If the name supplied to the Property method matches the name of an existing property (a shadow property or one defined on the entity class), then the code will configure that existing property rather than introducing a new shadow property.
Source
In my scenario I wanted the owned-type property to be auto-initialed in the parent class:
public class User
{
EmailPermissions _EmailPermissions;
public EmailPermissions
{
get => _EmailPermissions ??= new EmailPermissions();
set => _EmailPermissions = value;
}
}
When I tried to add seed data I got that nasty exception.
The solution was to pass the User as anonymous type in its HasData call.
I had the same issue seeded my data at startup.
Here is the link to the github issue.
In my case, I was using anonymous types, but the default class constructor was instantiating an instance of the non-nullable child class, which contained the property in question. The fix was to either not instantiate the child class in the default constructor, or use a different constructor that did not instantiate the child class.
Original
public class BaseDownload {
[Key]
public Guid BaseDownloadId { get; set; }
public Guid DownloadCategoryId { get; set; }
public virtual DownloadCategory DownloadCategory { get; set; }
public BaseDownload()
{
this.BaseDownloadId = Guid.NewGuid();
this.DownloadCategory = new DownloadCategory();
}
}
followed by the seed data:
modelBuilder.Entity<BaseDownload>().HasData(
new BaseDownload()
{
BaseDownloadId = Guid.Parse("9150ebd7-dd84-4c97-bf58-62f1c3611545"),
DownloadCategoryId = Guid.Parse("46b087f9-5c71-401f-a5cf-021274463715"),
}
);
Trying to seed the data gave the error "The seed entity for entity type 'BaseDownload' cannot be added because it has the navigation 'DownloadCategory' set. To seed relationships, add the entity seed to 'BaseDownload' and specify the foreign key values {'DownloadCategoryId'}.".
An instance of the child class (DownloadCategory) definitely exists as it was created using the same set of seed data. So I only needed to use the ID property. Adding a new constructor that did not instantiate the child DownloadCategory() class like below resolve the error.
public BaseDownload(bool isSeedData)
{
}
and
modelBuilder.Entity<BaseDownload>().HasData(
new BaseDownload(true)
{
BaseDownloadId = Guid.Parse("9150ebd7-dd84-4c97-bf58-62f1c3611545"),
DownloadCategoryId = Guid.Parse("46b087f9-5c71-401f-a5cf-021274463715"),
}
);
I am trying to use EF to export/import the existing database of a DbContext. In this context, there are several entities with Guid Id properties with DatabaseGeneratedOption.Identity defined by the ModelBuilder. When I re-import the entities, I want to use the Id value from the serialized object, but it always generates a new Id value when I save the changes. Is there any way to force EF to use my Id value in this case? I know DatabaseGeneratedOption.None will allow me to do it, but then I will always be responsible for generating the Id. I know there segmentation issues of the index that occur without using sequential Guids, so I do not want to do this.
Am I out of luck or has anyone found a trick?
Update: we have decided to simply change all Guid Id from DatabaseGeneratedOption.Identity to DatabaseGenerationOption.None and provide the Id ourselves. Although this leads to index fragmentation, we do not expect this to be a problem with the smaller size of our tables.
You can achieve what you want by defining two contexts that derive from a base context. One context defines its keys with DatabaseGeneratedOption.Identity, the other one with DatabaseGeneratedOption.None. The first one will be your regular application's context.
This is possible by virtue of Guid primary keys not being real identity columns. They're just columns with a default constraint, so they can be inserted without a value, or with a value without having to set identity_insert on.
To demonstrate that this works I used a very simple class:
public class Planet
{
public Guid ID { get; set; }
public string Name { get; set; }
}
The base context:
public abstract class BaseContext : DbContext
{
private readonly DatabaseGeneratedOption _databaseGeneratedOption;
protected BaseContext(string conString, DatabaseGeneratedOption databaseGeneratedOption)
: base(conString)
{
this._databaseGeneratedOption = databaseGeneratedOption;
}
public DbSet<Planet> Planets { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Planet>().HasKey(p => p.ID);
modelBuilder.Entity<Planet>().Property(p => p.ID)
.HasDatabaseGeneratedOption(this._databaseGeneratedOption);
base.OnModelCreating(modelBuilder);
}
}
The context subclasses:
public class GenerateKeyContext : BaseContext
{
public GenerateKeyContext(string conString)
: base(conString, DatabaseGeneratedOption.Identity)
{ }
}
public class InsertKeyContext : BaseContext
{
public InsertKeyContext(string conString)
: base(conString, DatabaseGeneratedOption.None)
{ }
}
I first run some code to create and seed the source database:
var db1 = #"Server=(localDB)\MSSQLLocalDB;Integrated Security=true;Database=GuidGen";
var db2 = #"Server=(localDB)\MSSQLLocalDB;Integrated Security=true;Database=GuidInsert";
// Set initializers:
// 1. just for testing.
Database.SetInitializer(new DropCreateDatabaseAlways<GenerateKeyContext>());
// 2. prevent model check.
Database.SetInitializer<InsertKeyContext>(null);
using (var context = new GenerateKeyContext(db1))
{
var earth = new Planet { Name = "Earth", };
var mars = new Planet { Name = "Mars", };
context.Planets.Add(earth);
context.Planets.Add(mars);
context.SaveChanges();
}
And a target database:
using (var context = new GenerateKeyContext(db2))
{
context.Database.Initialize(true);
}
Finally this is the code that does the actual job:
var planets = new List<UserQuery.Planet>();
using (var context = new GenerateKeyContext(db1))
{
planets = context.Planets.AsNoTracking().ToList();
}
using (var context = new InsertKeyContext(db2))
{
context.Planets.AddRange(planets);
context.SaveChanges();
}
Now in both databases you'll see two records with identical key values.
You might wonder: why can't I use one context class, and construct it either with or without the Identity option? That's because EF builds the EDM model only once for a context type and stores it in the AppDomain. So the option you use first would determine which model EF will use for your context class.
For the first time ever I'm using GUIDs for PK values in my POCO classes which has presented a rather irritating problem being that I can't seem to get data added to my tables.
Here's an example addition:
public partial class EntityType
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }
public string Type { get; set; }
public bool Deleted { get; set; }
}
Here's my migration configuration seed method:
internal sealed class Configuration : DbMigrationsConfiguration<AC.WebGUI.Models.OrtundDataModel>
{
protected override void Seed(AC.WebGUI.Models.OrtundDataModel context)
{
// Prepopulate Entity Types.
if (!context.EntityTypes.Any())
{
context.EntityTypes.AddOrUpdate(
new EntityType { Id = Guid.NewGuid(), Type = "Supplier" },
new EntityType { Id = Guid.NewGuid(), Type = "Distributor" },
new EntityType { Id = Guid.NewGuid(), Type = "Staff" },
new EntityType { Id = Guid.NewGuid(), Type = "Customer" },
new EntityType { Id = Guid.NewGuid(), Type = "Pharmacy" }
);
context.SaveChanges();
}
}
Note that in both classes, I'm specifying a Guid.NewGuid() value for my Id property.
This, however, has not got me around the error:
System.Data.SqlClient.SqlException: Cannot insert the value NULL into column 'Id', table 'Ortund.dbo.EntityTypes'; column does not allow nulls. INSERT fails.
Obviously calling Guid.NewGuid() can't be a null value so I'm very unsure of how to proceed at this point. I figured that if I added it to the constructor on the class that defines the table, I could remove it from the actual implementation of that class, however, whether I take it out of the constructor or out of the implementation, the error persists.
How can I get these Guids added to my table data without any errors? What am I missing?
Edit - Addressing duplicate question
Since my question has been marked as a duplicate of this question which deals with using the specified data annotation to make EF automatically generate the GUID value for the PK column on the table, I thought I'd just explain how mine is different.
The idea is the same, true enough, but the implementation has diverged from what is discussed in the other question so I've arrived at a totally different solution for my own project which involved removal of this data annotation whereas the solutions proposed in the other question keep it and make other changes to the data model.
Figured I'd answer this before it gets closed.
Per comments, simply removing the DatabaseGenerated(DatabaseGeneratedOption.Identity)] annotation from the PK property fixed the problem though I did keep the assignment in the constructor.
I think I am missing something simple here. I am getting the error:
"Violation of PRIMARY KEY constraint 'PK_FeatureTypes'. Cannot insert duplicate key in object 'dbo.FeatureTypeCodes'. The duplicate key value is (28).\r\nThe statement has been terminated"
I have a look-up / linked table of FeatureType - (Mountain, Lake, River, etc.) which is already populated with data and is defined as:
[Table("FeatureTypeCodes")]
public class FeatureTypeCode {
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int FeatureTypeCodeID { get; set; }
public string Name { get; set; }
}
This is linked to my place table / object like this:
[Table("Places")]
public class Place {
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int PlaceID { get; set; }
public string Name { get; set; }
public FeatureTypeCode FeatureTypeCode { get; set; }
public ICollection<PlaceCoordinate> PlaceCoordinates { get; set; }
}
Then I am loading them from the old database like this (it is part of my conversion code):
foreach (DataRow r in table.Rows) {
int ftID = Convert.ToInt32(r["FeatureTypeId"]);
Place temp = new Place {
PlaceID = Convert.ToInt32(r["PlaceID"]),
Name = r["PlaceName"].ToString(),
FeatureTypeCode = featureTypeCodeRepository.FeatureTypeCodes.FirstOrDefault(o=>o.FeatureTypeCodeID == ftID)
};
places.Add(temp);
}
The error is being generated when it tries to insert a new FeatureType object with the same ID as an existing object while saving a Place. My thought was that by loading FeatureType from the context it would not attempt to insert a new FeatureType on saving the Place object. I am obviously wrong on that, but is it something simple I am missing?
I don't think that you use the same DBContext Object in your featureTypeCodeRepository and the places.Add(temp);. So I think that basically EF don't keep track of the FeatureTypeCodes becuse it's loaded by one context, and saved by another.
While I think that Simon Edström is right (+1), you may also consider to expose the primitive foreign key field (something like FeatureTypeId?) in your Place class. Then you can simply set
FeatureTypeId = ftID;
If you're not sure whether the FK field value really exists in the FeatureTypeCodes table, you can query for its existence using the featureTypeCodeRepository even when it has a different context. Using Any() is the cheapest way to do that:
var exists = featureTypeCodeRepository.FeatureTypeCodes
.Any(o => o.FeatureTypeCodeID == ftID)
It is not uncommon to do this in entity framework. Relationships consisting of only a reference (like Place.FeatureTypeCode) are called independent associations, those with a reference and a primitive FK property foreign key associations. Julia Lerman in her book DbContext says
unless you have a very
good reason not to expose the foreign key properties you will save yourself a lot of pain
by including them