I have a model for a UserProfile which contains a field for when the user is created. I would like EF Core to automatically set the value of this field to the current datetime, whenever a new row is added to the database. To achieve this, I have made the following model:
public class UserProfile
{
public UserProfile(string username, string email)
{
Username = username ?? throw new ArgumentNullException(nameof(username));
Email = email;
}
public Guid Id { get; init; } = Guid.NewGuid();
public string Username { get; init; }
public string Email { get; private set; } = "";
public DateTime Created { get; init; }
}
In the DbContext I have overridden the OnModelCreating() method and specified that the field UserProfile.Created should by default be set to the SQL value GETDATE() (I have also tried with CURRENT_TIMESTAMP).
public class UserProfileContext : DbContext
{
public UserProfileContext(DbContextOptions<UserProfileContext> options) : base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<UserProfile>().ToTable("Account");
modelBuilder.Entity<UserProfile>()
.Property(u => u.Created)
.HasDefaultValueSql("GETDATE()");
}
public DbSet<UserProfile> UserProfiles { get; set; }
}
But when I create a new user and request it back, I just get the following response:
[
{
"id": "3a33c8ad-7581-4adc-8c91-a65d40ec008e",
"username": "string",
"email": "string",
"created": "0001-01-01T00:00:00"
}
]
I cannot look directly in the database, since I'm using EF Core InMemory. What am I missing here?
Related
I have something like these:
BaseDTO
public record BaseDTO
{
public virtual int Id { get; protected init; }
public DateTime Timestamp { get; protected init; }
}
NotificationDTO
public record NotificationDTO(string Code, string Name, string Description, NotificationType Type) : BaseDTO;
NotificationsController
[HttpPost]
[ProducesResponseType((int)HttpStatusCode.OK)]
public async Task<IActionResult> Post([FromBody] NotificationDTO notificationDTO)
{
await _notificationsService.AddNotification(notificationDTO);
return Ok();
}
When I open SwaggerURL only members of NotificationsDTO can be set in "Try it out" section, members of BaseDTO are missing. Even if I add them by hand like setting the Id to 1 in Swagger, if I put a breakpoint in controller, Id it's 0, so Swagger doesn't modify it.
How can I see the base entity members in Swagger?
Edit:
It seems the object binding is not done properly, I have tried to send this in the request body with Postman:
{
"type": 0,
"id": 1,
"timestamp": "2022-05-13T09:22:18.429Z",
"code": "string",
"name": "string",
"description": "string"
}
And in the controller I've got this:
{
"NotificationDTO"{
Id = 0,
"Timestamp = 1/1/0001 12":"00":00 AM,
"Code = string",
"Name = string",
"Description = string",
"Type = Email"
}
}
If I change the access modifiers of the properties of base entity from public virtual int Id { get; protected init; } to public virtual int Id { get; init; } then the objects are binded properly and I can see the members of the base entity in Swagger.
I'm not sure how to explain our goal in words, so I'll hop straight to the code.
With our current json converter settings. We get the following result when converting one of our events.
{
"PortfolioId": {
"Id": "portId"
},
"EntityId": {
"Id": "3cf7582b-3cad-4aeb-a671-0132ba97d60d"
},
"EventVersion": 1,
"OccurredOn": "2018-08-08T09:52:03.7871323+02:00",
"Id": "71fe3a2e-354a-4b19-abea-655471e96d72",
"Creator": {
"Id": "27a1d6b1-1ffa-4071-92ee-31c12bf120f0"
},
"CorrelationId": "3138dbe0-3a4d-4559-83e9-d1f3e5684ee8"
}
Our goal is to get a converted event that looks like this;
{
"PortfolioId": "portId",
"EntityId": "3cf7582b-3cad-4aeb-a671-0132ba97d60d",
"EventVersion": 1,
"OccurredOn": "2018-08-08T09:52:03.7871323+02:00",
"Id": "71fe3a2e-354a-4b19-abea-655471e96d72",
"Creator": "27a1d6b1-1ffa-4071-92ee-31c12bf120f0",
"CorrelationId": "3138dbe0-3a4d-4559-83e9-d1f3e5684ee8"
}
In the event we have an object of a certain type (i.e EntityId, PortfolioId) which holds the value in a property. All these Id types derive from an abstract class with the property "Id".
An event class looks like this.
public class ExampleEvent : DomainEvent
{
public PortfolioId PortfolioId { get; }
public EntityId EntityId { get;}
public ExampleEvent(
PortfolioId portfolioId,
EntityId entityId,
UserId creator, Guid correlationId) : base(creator, correlationId)
{
PortfolioId = portfolioId;
EntityId = entityId;
}
}
Does anybody have an idea how one could do this. I thought this might be possible with a custom json converter but have no idea yet on how to implement this for this case.
EDIT: I should have stated that this has to be done one many event types. And that a generic reusable solution seems to fit best in order to keep the overhead low. This mean that it is probably best if the event class itself is not altered at all. (so preferably without attributes etc)
The second Approach in this answer could help in manipulating the serialization.
Making a property deserialize but not serialize with json.net
You can use JsonIgnore attributes with calculated properties:
public class PortfolioId
{
public string Id { get; set; }
}
public class EntityId
{
public string Id { get; set; }
}
public class UserId
{
public string Id { get; set; }
}
public class ExampleEvent
{
private ExampleEvent() // for JSON deserializer
{
Creator = new UserId();
Portfolio = new PortfolioId();
Entity = new EntityId();
}
// add your base constructor call
public ExampleEvent(PortfolioId portfolio, EntityId entity, UserId creator)
{
Creator = creator;
Portfolio = portfolio;
Entity = entity;
}
[JsonIgnore]
public UserId Creator { get; set; }
public string CreatorId
{
get => Creator.Id;
set => Creator.Id = value;
}
[JsonIgnore]
public PortfolioId Portfolio { get; set; }
public string PortfolioId
{
get => Portfolio.Id;
set => Portfolio.Id = value;
}
[JsonIgnore]
public EntityId Entity { get; set; }
public string EntityId
{
get => Entity.Id;
set => Entity.Id = value;
}
public int EventVersion { get; set; }
public string Id { get; set; }
public string CorrelationId { get; set; }
}
Incase JsonIgnore does not suites your need or you need more customization you may also look for IContractResolver with JsonProperty.ShouldDeserialize / JsonProperty.ShouldSerialize. here some examples.
Code:
[HttpPost("user/register")]
public IActionResult Register([FromBody] User user)
{
if (user?.Name is null || user?.Password is null)
{
return BadRequest(new {message = "User is null and/or name and password are not provided"});
}
else
{
// Add to db
return Json(user);
}
}
Also the User class:
public class User
{
public Guid Id { get; set; }
public string Name { get; set; }
public string Password { get; set; }
public string Role { get; set; }
}
It should basically get a user and add it to the database. I tried sending this json:
{ "Name": "Batman", "Password": "IronmanSucks"}
The app caught the request, but the user object was null. I also tried with:
{ "user": { "Name": "Batman", "Password": "IronmanSucks"} }
But according to this documentation, the first json should have worked fine.
Here is a link to an example http request in postman
Does this have to do with the headers or is it a bug in .NET Core 2.0?
This can only happen if the type does not have a parameterless constructor, thus this can be simply fixed by adding such.
I believe that the Model is coming up as invalid hence why it is null.
You should try adding a [BindNever] attribute into the User class for the Role and Guid properties, seeing as you aren't using them.
If that doesn't work you may try using extended classes like so:
public class User
{
public string Name { get; set; }
public string Password { get; set; }
}
public class DataUser : User
{
public Guid Id { get; set }
public string Role { get; set; }
}
If you're using MVC Core instead of MVC, make sure you add Json Formaters (from Microsoft.AspNetCore.Mvc.Formatters.Json). In your Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services
.AddMvcCore()
.AddJsonFormatters();
}
This should help the [FromBody] to de-serialize your Post content
I set deafaultValue in migration
AlterColumn("Users", "NotNullValue", c => c.String(nullable: false, defaultValue: "Work it!"));
then i try add new object, and SqlProfiler show me query
INSERT [dbo].[WowTable]([NotNullValue],[OnceMoreValue]) VALUES (NULL, 'val')
that throw "cannot insert the value null into column"
is that any way to stop inserting and updating property when it null?
UPD:
Domain class:
public class User
{
public string NotNullValue { get; set; }
public int Id { get; set; }
public string OnceMoreValue { get; set; }
}
Update method:
public Task AddOrUpdate<TEntity>(TEntity entity) where TEntity : class, IEntity
{
_dbContext.Set<TEntity>().AddOrUpdate(x => x.Id, entity);
return _dbContext.SaveChangesAsync();
}
calling:
AddOrUpdate(new User{OnceMoreValue = "val"});
You can either validate the model using [Required] attribute for NotNullValue property:
public class User
{
[Required]
public string NotNullValue { get; set; }
public int Id { get; set; }
public string OnceMoreValue { get; set; }
}
Ref: https://msdn.microsoft.com/en-in/data/gg193959.aspx
Or from what I see in your migration script, you have a default value for this property ("Work It"). So in your User class constructor, you can set NotNullvalue = "Work It" so that this default value will be saved in the case you mentioned:
AddOrUpdate(new User{OnceMoreValue = "val"});
I have a class like this:
[Table("member_activation")]
public partial class MemberActivation
{
[Key]
public Int64 member_id { get; set; }
public String token { get; set; }
}
My db:
public class SMADbContext : DbContext
{
public SMADbContext() : base("SMADB")
{
Database.SetInitializer<SMADbContext>(new NullDatabaseInitializer<SMADbContext>());
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
}
public DbSet<Member> Members { get; set; }
public DbSet<MemberActivation> MemberActivations { get; set; }
public DbSet<ApiAccount> ApiAccounts { get; set; }
public DbSet<ApiHardware> ApiHardwares { get; set; }
public DbSet<MemberRelation> MemberRelations { get; set; }
}
In my controller:
[Route("tester")]
[AllowAnonymous]
public IHttpActionResult tester()
{
using (var db = new SMADbContext())
{
var memberActivation = new MemberActivation();
memberActivation.member_id = 10155;
memberActivation.token = "hello";
db.MemberActivations.Add(memberActivation);
return Json(new { dbset = db.MemberActivations.ToList(), memberAct = memberActivation });
}
}
db.MemberActivations.Add(memberActivation); does not work. When I return the json, the dbset does not include the newly created memberActivation. I do not have db.SaveChanges() because it will not save until the memberActivation is pushed to the dbset
You cant set member_id, it is the key and ef uses it as identity. It will be ignored. You can configure ef so that member_id is not identity but that's another topic.
db.MembershipActivations.Add( new MemberActivation { token = "hello"}):
db.SaveChanges();
should work fine.
if however , as it would appear , you have an existing member and you are trying to set a relationship with that entity via a join table. Then you should retrieve that entity and set the memberactivation. Ef will sort the rest out for you. Bit of guessing here as i would need to see the involved entities.