Entity Framework with Nullable Guid? causing Validation Error - c#

I am trying to use Nullable Guid in Entity Framework but when i tried to commit the change i am getting validation message due to foreign key is null, I have below domain class, DomainType Class has child DomainCustomField :-
DomainType.cs
public class DomainType
{
public Guid? Id
{
get;
set;
}
private IList<DomainCustomField> _domainCustomFields;
public string Name
{
get;
set;
}
public Guid? AccountId
{
get;
set;
}
public virtual IList<DomainCustomField> DomainCustomFields
{
get
{
return this._domainCustomFields;
}
set
{
this._domainCustomFields = value;
}
}
}
DomainCustomField.cs
public class DomainCustomField
{
public Guid? Id
{
get;
set;
}
public string Name
{
get;
set;
}
public Guid? DomainTypeId
{
get;
set;
}
}
below is mapping :-
modelBuilder.Domain<DomainCustomField>().HasKey((DomainCustomField p) => new
{
p.Id,
p.DomainTypeId
});
modelBuilder.Domain<DomainCustomField>()
.Property(p => p.Id)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
modelBuilder.Domain<DomainType>()
.HasMany(p => p.DomainCustomFields)
.WithRequired()
.HasForeignKey<Guid?>(x => x.DomainTypeId);
below is sample code which i tried to insert.
DomainType domainType = new DomainType()
{
AccountId = customer.Id
};
domainType.DomainCustomFields.Add(new DomainCustomField()
{
Name = "Test"
});
Workspace.Add<DomainType>(domainType);
Workspace.CommitChanges();
Error :- validation error for DomainTypeId is required
in the same code if i change Guid? to Guid in ID variable then its work correctly. I don't know how to handle nullable Guid. do i need to manually set parent key in child reference? then how it works fine in case of Guid?
Note :- I am using using Guid because i am working with distributed database, where id can generate from multiple source system.
I am using Guid? because by default Guid generate 00000000-0000-0000-0000-000000000000 for empty Guid which can grow database size.

The issue has nothing to do with Guid type (the same will happen with int), but the discrepancy between the nullability of the property type used in the entity and the actual nullability of the property implied by the model.
First, EF requires PKs to be non nullable. Hence DomainType.Id, DomainCustomField.Id and DomainCustomField.DomainTypeId are effectively non nulable (it's unclear why you have include DomainTypeId in the PK of the DomainCustomField which already have unique database generated Id, but that's another story).
Second, DomainCustomField.DomainTypeId is effectively non nullable because you have specified that - the WithRequired() part of the FK relationship configuration.
Now, EF does its best to handle these discrepancies. If you look at the generated migration, you will see that the corresponding database table columns of all the fields in question are non nullable. However (nothing is perfect, you know) some part of the EF code is failing to handle that discrepancy and hence the exception.
With that being said, always keep the model properties nullability in sync with their actual nullability. Which in you case is to make both 3 properties non nullable (i.e. of type Guid).
P.S. What about the note I am using Guid? because by default Guid generate 00000000-0000-0000-0000-000000000000 for empty Guid which can grow database size., I have no idea what do you mean by that. The nullablitity of the class property has nothing to do with the database size, and as I already mentioned, the corresponding database table columns for such setup are non nullable anyway.
Also note that in your example you forget to set the Id of the new DomainType, which you should because it's not auto generated.

I'll take a slightly different approach in my answer and try and tell you what to change in order to get a working example;
Like someone has pointed out, unique identifiers must not be nullable so the Id field in DomainType should a Guid. Also you will need to change the Id field in DomainCustomField. These may need an empty constructor just to create a new Guid.
Secondly, the foreign key in your other object Guid? DomainTypeId is perfectly fine, this can stay, but if it is staying you will need to change your configuration.
In this block you have specified that the property is required?
modelBuilder.Domain<DomainType>()
.HasMany(p => p.DomainCustomFields)
.WithRequired()
.HasForeignKey<Guid?>(x => x.DomainTypeId);
So simply make it optional;
modelBuilder.Domain<DomainType>()
.HasMany(p => p.DomainCustomFields)
.WithOptional()
.HasForeignKey<Guid?>(x => x.DomainTypeId);
That should solve your issues. Any questions let me know :)

You can use a standard identifier for the database. GUID? stored as a field in the table.

Related

SQLite seems to have a problem with [DatabaseGenerated(DatabaseGeneratedOption.Identity)]

I have a problem with the SQLite in-memory database. The normal database is working.
This is my model code
public class Log
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }
public string Message { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public DateTime CreatedAt { get; set; }
}
The SQL statement to create the table
CREATE TABLE [dbo].[AuditLogs]
(
[Id] UNIQUEIDENTIFIER NOT NULL DEFAULT newid() PRIMARY KEY,
[Message] varchar(max) NOT NULL CONSTRAINT ensure_json CHECK (ISJSON([Message])> 0),
[CreatedAt] datetime NOT NULL default GetDate()
)
The error
Microsoft.Data.Sqlite.SqliteException (0x80004005): SQLite Error 19: 'NOT NULL constraint failed: AuditLogs.CreatedAt'.
Do you have any solution?
You could just use this:
public Guid Id { get; set; } = Guid.NewGuid();
The problem with the Entity Framework is that it won't generate keys for you. If it is database-generated then some trigger in the database would still need to create this ID. This is generally done when the ID is of type int, but not Guid. Then again, SQLite is a weird database provider to begin with as it doesn't really has datatypes. Data type definitions are more suggestions and not enforced by the engine. (But EF will enforce it.)
Anyways, since you use Guids there's nothing wrong with assigning new values to the property, as they will be overwritten by the value in the database on retrieval. But SQLite isn't really generating values for you.
Also, I would use public DateTime CreatedAt { get; set; } = DateTime.Now(); for the same reason. I myself actually had similar problems but I use the Fluid API instead and use this:
var hostBuilder = modelBuilder.Entity<Host>();
hostBuilder
.Property(r => r.Id)
.HasColumnOrder(0)
.IsRequired()
.HasColumnName("Key")
.HasColumnType("varchar(36)")
.HasComment($"Primary key");
hostBuilder
.Property<DateTime>("Created")
.HasColumnOrder(1)
.HasDefaultValueSql("CURRENT_TIMESTAMP")
.ValueGeneratedOnAdd()
.HasComment($"When was it created?");
hostBuilder
.HasKey(r => r.Id)
.HasName($"PK_Visitor_Host_Key");
And my class only has the Id property defined, as I don't need the Created field in my project. It still gets added, though! The HasDefaultValueSql() call will tell that the field is database-generated, including how it's generated. You might want to look into this Fluid API for your project. I prefer it over those attributes as it provides more options and better control, plus I can add fields to tables that are not important for my code, yet still required for other purposes...
(Btw. You don't want timestamps to be unique as two records could be created at exactly the same timestamp on fast systems.)

EF 6 calls INSERT instead of UPDATE

This could be a duplicate question but a lot of searching for the words in the title only got me a lot of unrelated results.
I have an entity that's roughly set up like this:
public abstract class A
{
public GUID AId { get; set; }
public string SomeProperty { get; set; }
}
public class B : A
{
public string SomeOtherProperty { get; set; }
}
The context has public DbSet<B> BInstances { get; set; } for B objects. In OnModelCreating, the mapping has A set to ignored and B is mapped to a table called TableB.
The AId field is not auto-generated (not an identity field) but it's set to be primary key, both in the database and in the mapping. In the database, the field is defined as a non-null uniqueidentifier with no default.
At runtime, I'm loading an instance of B using its key (_token is just a CancellationToken):
var b = await (dbCtx.BInstances.FirstOrDefaultAsync(e => e.AId), _token));
Then, a property of b is set and I try to save it back to database:
b.SomeOtherProperty = "some new text";
await (dbCtx.SaveChangesAsync(_token));
At this point, I'm getting a Violation of PRIMARY KEY constraint error from the database, stating that the value of AId cannot be inserted because it'd be a duplicate. Of course, the ID is already in the database, I loaded the entity from there, using the ID. For some reason, EF generates an INSERT statement, not an UPDATE and I don't understand why.
When I check dbCtx.Entry(b).State, it's already set to EntityState.Modified. I'm at a loss - can someone point out what I'm doing wrong? I never had issues with updating entities before but I haven't used EF with GUID primary keys (usually I use long primary keys).
I'm using EF 6 and .NET Framework 4.7.1.
Thank you all for the suggestions - this turned out to be a mapping problem that I caused.
In my OnModelCreating() call, I called MapInheritedProperties() on a type that didn't inherit from a base class (other than object, of course) - this seems to have triggered a problem. Other entities that do share a base class worked fine with the mapping call.
I also called ToTable() directly against the entity class - this broke my table mapping for reasons I do not understand. Once I moved that call inside Map(), it started working as expected.
So I went from this:
entity.ToTable("tablename");
to this:
entity.Map(m => m.ToTable("tablename"));
to solve the problem.
Hopefully this will be useful for future readers.
try this
b.SomeOtherProperty = "some new text";
dbCtx.BInstances.AddOrUpdate(b);
await (dbCtx.SaveChangesAsync(_token));
AddorUpdate will update your b instance if it is already added.

Mapping One-to-Zero-or-One with EF7

I am currently in the process of cleaning up a fairly large database. Part of the database has a relationship which is a one-to-zero-or-one mapping. Specifically:
User -> UserSettings
Not all users will have user settings, but a user setting cannot exist without the user. Unfortunately, the tables already exist. User has an PK ID. UserSettings has a PK ID and a column, User_Id_Fk which, at this point in time, is not a true FK (there is no relationship defined).
I'm in the process of fixing that and have done so from the DB perspective through SQL and have confirmed with tests. (Added the FK constraint. Added a unique constraint on User_Id_Fk.) This was all done on the UserSettings table. (Note: I am not using EF Migrations here. I have to manually write the SQL at this point in time.)
However, I now need to wire up an existing application to properly handle this new mapping. The application is using ASP.NET Core 1.0 and EF7. Here are (shortened) versions of the existing data models.
public class User
{
public int Id { get; set; }
public virtual UserSettings UserSettings { get; set; }
}
public class UserSettings
{
public int Id { get; set; }
[Column("User_Id_Fk")]
public int UserId { get; set; }
[ForeignKey("UserId")]
public virtual User User { get; set; }
}
I have this Fluent Mapping as well:
builder.Entity<UserSettings>()
.HasOne(us => us.User)
.WithOne(u => u.User)
.IsRequired(false);
When I go to run the application and access these items in the database, I get this error followed with a cryptic set of messages that has no information relating directly back to my application.:
ArgumentNullException: Value cannot be null.
Parameter name: navigation
Microsoft.Data.Entity.Utilities.Check.NotNull[T] (Microsoft.Data.Entity.Utilities.T value, System.String parameterName) <0x10d28a650 + 0x00081> in <filename unknown>, line 0
After doing research, someone had mentioned that the ID of the UserSettings class must be the same as the foreign key, like so:
public class UserSettings
{
[Key, ForeignKey("User")]
public int Id { get; set; }
public virtual User User { get; set; }
}
I don't really have this as an option as the DB is being used for other applications I have no control over at this point. So, am I stuck here? Will I just have to maintain a 1:many mapping (which could happen now, though it hasn't) and not have proper constraints for a 1:0..1 mapping?
Update
Looking at octavioccl's answer below, I tried it out without any success. However, I then removed User from the mapping in UserSettings (but I left UserId). Everything appeared to work as far as I can tell. I'm really confused what is going on here, however, and if this is even the right answer, or if I'm just getting lucky.
Remove the data annotations and try with these configurations:
builder.Entity<UserSettings>()
.Property(b => b.UserId)
.HasColumnName("User_Id_Fk");
builder.Entity<User>()
.HasOne(us => us.UserSettings)
.WithOne(u => u.User)
.HasForeignKey<UserSettings>(b => b.UserId);
From EF Core documentation:
When configuring the foreign key you need to specify the dependent
entity type - notice the generic parameter provided to HasForeignKey
in the listing above. In a one-to-many relationship it is clear that
the entity with the reference navigation is the dependent and the one
with the collection is the principal. But this is not so in a
one-to-one relationship - hence the need to explicitly define it.
The example that is presented in the quoted link (Blog-BlogImage) is pretty much the same of what are you trying to achieve.
If the solution that I show above doesn't work, then you should check if User_Id_Fk column allows null. If that is the case, change the FK property type to int?:
public class UserSettings
{
public int Id { get; set; }
public int? UserId { get; set; }
public virtual User User { get; set; }
}

Nullable property raises validation error while it shouldnt

Using Entity Framework with Breeze, I have this class Taxi with nullable int TravelID:
public class Taxi
{
// some primary key/Id stuff
// Then these:
public int? TravelID { get; set; }
public virtual Travel Travel { get; set; }
}
(simplified of course) Mapping looks like this:
public TaxiMap()
{
this.Property(t => t.TravelID).IsOptional();
}
My database tool (HeidiSQL) shows that the property is nullable and the default is also NULL. But when I try to save a Taxi entity I get this validation error:
errorMessage: "'TravelID' is required"
The TravelID was required before but I changed that like this. This error occurs when the TravelID is null. When it's 0 I get a FK constraint error.
So now my question is, did I forget anything/do something wrong to make the nullable work?
Added after 1 hour: I have found a workaround for now but it's really dirty and i'd really prefer not using it. In the front end I set the TravelID to '0' to pass the validation, then in my controller I set the Taxi's TravelID to NULL before saving.
The TravelID is not the actual ID of your Taxi class, it is a foreign key to the Travel class. The mapping you're trying to make work here should be in the TravelMapper, as it belongs there.
If you take it to the next step, you can omit all of this and use attributes on your models, such as the autogeneration attribute above the ID property:
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
This way you ensure the Travel class must have an ID but in the Taxi class there is nothing that obligates you to specify an object of type Travel.

Implicit Key for a one-to-many relationship in EF code first

I have an aggregate class that will contain a collection of another class, but that class will only ever exist in a collection on that aggregate, so I have no need for an ID on it in my code, or a need for a reference to the aggregate. For example:
public class SalesListing
{
public Guid Id { get; set; }
public ICollection<LocalizedDescription> Descriptions { get; set; }
}
public class LocalizedDescription
{
public string CultureCode { get; set; }
public string Title { get; set; }
}
I'd like to just declare the key for the LocalizedDescription class as a combination of SalesListingId and CultureCode without creating a SalesListingId property or reference back to SalesListing. Any way to do this with EF 5.0 ?
For example, here's an example how I'd imagine such an API would look like if it exists:
modelBuilder.Entity<LocalizedDescription>().BelongsTo<SalesListing>(s => s.Description)
.WithKey((s, ld) => new { s.Id, ld.CultureCode })
No, you will still need to specify an ID
You can define composite keys using the following syntax:
modelBuilder.Entity<LocDesc>().HasKey(ld => new { ld.CultureCode, ld.Title });
That said, I think you will regret not defining an integer primary key. If you ever need to export the contents (such as for an external review or translation), matching records by ID will be faster and less error prone than matching by title. Having the ID doesn't hurt and is easier to include now than to retrofit later.
I think you can specify the FK mapping implicitly by not supplying a specific property for the relation in the mapping definition:
modelBuilder.Entity<SalesListing>().HasMany( e => e.Descriptions ).WithRequired();
Should you need to specify the foreign key property, introduce a SalesListingID property for the value and use the following mapping instead:
modelBuilder.Entity<SalesListing>().HasMany( e => e.Descriptions ).WithRequired()
.HasForeignKey( r => r.SalesListingID );

Categories

Resources