How to update complex type field (json) using ormLite from servicestack - postgresql

I am trying to update only one column with jsonb type. Insert works perfectly without any surprises but I can't find out how can I do update only one field with attribute [ComplextType('json')]
db.UpdateOnly(() => new QuestionPoco() {Answers = requestDto.answers},
where: q => q.Id.ToString() == question.id.ToString());

This should now be supported from this commit which is now available on MyGet.
We've also added new typed PgSqlTypes Attributes which you can use instead of [CustomField("json")], e.g:
public class Question
{
public int Id { get; set; }
public string Text { get; set; }
//equivalent to: [CustomField("json")]
[PgSqlJson]
public List<Answer> Answers { get; set; }
}
public class Answer
{
public int Id { get; set; }
public string Text { get; set; }
}
Which you can Insert/Update as normal, e.g:
db.DropAndCreateTable<Question>();
var createTableSql = db.GetLastSql();
Assert.That(createTableSql, Does.Contain("\"answers\" json NULL"));
db.Insert(new Question
{
Id = 1,
Answers = new List<Answer>
{
new Answer { Id = 1, Text = "Q1 Answer1" }
}
});
var question = db.SingleById<Question>(1);
Assert.That(question.Answers.Count, Is.EqualTo(1));
Assert.That(question.Answers[0].Text, Is.EqualTo("Q1 Answer1"));
db.UpdateOnly(() => new Question {
Answers = new List<Answer> { new Answer { Id = 1, Text = "Q1 Answer1 Updated" } }
},
#where: q => q.Id == 1);
question = db.SingleById<Question>(1);
Assert.That(question.Answers[0].Text, Is.EqualTo("Q1 Answer1 Updated"));

Related

.NET Core MongoDB. Find by guid returns null

I have the following setup:
The document:
[BsonCollection("Users")] // I get the collection name with a custom extension
[BsonIgnoreExtraElements]
public class UserDocument
{
[BsonId]
public Guid Id { get; set; }
public string UserName { get; set; }
public string Email { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime DateOfBirth { get; set; }
public UserSettingsModel UserSettings { get; set; }
}
public class UserSettingsModel
{
// ...
}
The repository:
public class UserRepository
{
private readonly IMongoCollection<UserDocument> _collection;
private readonly ILogger<UserRepository> _logger;
public UserRepository(IMongoDatabase database, ILogger<UserRepository> logger)
{
// returns "Users"
var collectionName = typeof(UserDocument).GetCollectionName();
_collection = database.GetCollection<UserDocument>(collectionName);
}
// ...
public async Task<UserDocument> GetById(Guid id)
{
var filter = Builders<UserDocument>.Filter.Eq(x => x.Id, id);
var user = await _collection.FindAsync(filter);
// var user = await _collection.FindAsync(x => x.Id == id); - doesn't work either
var request = filter.Render(
_collection.DocumentSerializer,
_collection.Settings.SerializerRegistry).ToString();
_logger.LogDebug(request);
return user.FirstOrDefault();
}
}
And I initialize the client this way:
// ...
BsonSerializer.RegisterSerializer(new GuidSerializer(GuidRepresentation.Standard));
var client = new MongoClient(connectionString);
var database = client.GetDatabase(dbName);
services.AddSingleton(c => database);
// convention pack and registries
// ...
// if moved here doesn't work either
// BsonSerializer.RegisterSerializer(new GuidSerializer(GuidRepresentation.Standard));
The filter generated in GetById is still the following: { "_id" : CSUUID("459f165a-4a91-4f39-906c-dc7401ee2468") } when I expect it to be UUID instead of CSUUID.
So, the query doesn't find anything and returns null. In the database the document I'm searching for has _id: UUID('459f165a-4a91-4f39-906c-dc7401ee2468')
What am I doing wrong?
I was able to fixing by this trick:
var mongoConnectionUrl = new MongoUrl(connectionString);
var mongoClientSettings = MongoClientSettings.FromUrl(mongoConnectionUrl);
// before initializing client
mongoClientSettings.GuidRepresentation = GuidRepresentation.Standard;
However setting the GuidRepresentation in client settings is obsolete, which is quite confusing. Also, the query generated still has CSUUID instead of UUID. I was able to log the query the following way:
mongoClientSettings.ClusterConfigurator = cb =>
{
cb.Subscribe<CommandStartedEvent>(e => logger.LogDebug($"{e.CommandName} - {e.Command.ToJson()}"));
};
If anyone finds a better way and post it here it would be appreciated.

How to write an audit log entry per changed property with Audit.NET EntityFramework.Core

I'm trying to get the Audit:NET EntityFramework.Core extension to write an AuditLog entry per changed property.
For this purpose I've overidden the EntityFrameworkDataProvider.InsertEvent with a custom DataProvider.
The problem is, using DbContextHelper.Core.CreateAuditEvent to create a new EntityFrameworkEvent returns null.
The reason seems to be, at this point in the code execution DbContextHelper.GetModifiedEntries determines all EF Entries have State.Unmodified, even if they are clearly included in the EventEntry changes.
I'm trying to circumvent CreateAuditEvent by manually creating the contents is impossible due to private/internal properties.
Maybe there is an alternative solution to this problem I'm not seeing, i'm open to all suggestions.
Audit entity class
public class AuditLog
{
public int Id { get; set; }
public string Description { get; set; }
public string OldValue { get; set; }
public string NewValue { get; set; }
public string PropertyName { get; set; }
public DateTime AuditDateTime { get; set; }
public Guid? AuditIssuerUserId { get; set; }
public string AuditAction { get; set; }
public string TableName { get; set; }
public int TablePK { get; set; }
}
Startup configuration
Audit.Core.Configuration.Setup()
.UseCustomProvider(new CustomEntityFrameworkDataProvider(x => x
.AuditEntityAction<AuditLog>((ev, ent, auditEntity) =>
{
auditEntity.AuditDateTime = DateTime.Now;
auditEntity.AuditAction = ent.Action;
foreach(var change in ent.Changes)
{
auditEntity.OldValue = change.OriginalValue.ToString();
auditEntity.NewValue = change.NewValue.ToString();
auditEntity.PropertyName = change.ColumnName;
}
}
Custom data provider class
public class CustomEntityFrameworkDataProvider : EntityFrameworkDataProvider
{
public override object InsertEvent(AuditEvent auditEvent)
{
var auditEventEf = auditEvent as AuditEventEntityFramework;
if (auditEventEf == null)
return null;
object result = null;
foreach (var entry in auditEventEf.EntityFrameworkEvent.Entries)
{
if (entry.Changes == null || entry.Changes.Count == 0)
continue;
foreach (var change in entry.Changes)
{
var contextHelper = new DbContextHelper();
var newEfEvent = contextHelper.CreateAuditEvent((IAuditDbContext)auditEventEf.EntityFrameworkEvent.GetDbContext());
if (newEfEvent == null)
continue;
newEfEvent.Entries = new List<EventEntry>() { entry };
entry.Changes = new List<EventEntryChange> { change };
auditEventEf.EntityFrameworkEvent = newEfEvent;
result = base.InsertEvent(auditEvent);
}
}
return result;
}
}
Check my answer here https://github.com/thepirat000/Audit.NET/issues/323#issuecomment-673007204
You don't need to call CreateAuditEvent() you should be able to iterate over the Changes list on the original event and call base.InsertEvent() for each change, like this:
public override object InsertEvent(AuditEvent auditEvent)
{
var auditEventEf = auditEvent as AuditEventEntityFramework;
if (auditEventEf == null)
return null;
object result = null;
foreach (var entry in auditEventEf.EntityFrameworkEvent.Entries)
{
if (entry.Changes == null || entry.Changes.Count == 0)
continue;
// Call base.InsertEvent for each change
var originalChanges = entry.Changes;
foreach (var change in originalChanges)
{
entry.Changes = new List<EventEntryChange>() { change };
result = base.InsertEvent(auditEvent);
}
entry.Changes = originalChanges;
}
return result;
}
Notes:
This could impact performance, since it will trigger an insert to the database for each column change.
If you plan to use async calls to DbContext.SaveChangesAsync, you should also implement the InsertEventAsync method on your CustomDataProvider
The Changes property is only available for Updates, so if you also want to audit Inserts and Deletes, you'll need to add the logic to get the column values from the ColumnValues property on the event

Adding a model with join to another model for many to many without navigation property [duplicate]

This question already has an answer here:
Entity Framework - Inserting model with many to many mapping
(1 answer)
Closed 4 years ago.
How can I insert a model Tag that belongs to a model Post when I have the models setup like this:
Post
public class Post
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public virtual ICollection<Tag> Tags { get; set; }
public Post()
{
Tags = new List<Tag>();
}
}
Tag
public class Tag
{
public int Id { get; set; }
public string Name { get; set; }
}
This question suggests to create a Post object then add Tags to the Tags collection, I couldn't get it working:
Insert/Update Many to Many Entity Framework . How do I do it?
I want to add Tag to Post already in the database, how can I do that with EF. I'm new to EF.
This is what I've tried, if I send this to the API it doesn't insert any records and I can see that the new tag Id = 0 which doesn't exist in the database, but I'd think that'd cause a foreign key constraint error, not sure If I need to do something to auto generate Id for the tag:
{
Name: "test"
}
API
[ResponseType(typeof(Tag))]
public IHttpActionResult PostTag(Tag tag)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var post = new Post();
var tags = new List<Tag>();
tags.Add(tag);
post.Tags.Add(tag);
post.Id = 10;
db.Entry(post).State = EntityState.Modified;
db.SaveChanges();
return CreatedAtRoute("DefaultApi", new { id = tag.Id }, tag);
}
I fetch the Post first add then save changes.
[ResponseType(typeof(Tag))]
public IHttpActionResult PostTag(TagDTO tagDTO)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var post = db.Posts.Find(TagDTO.PostId);
post.Tags.Add(new Tag() { Name = tagDTO.Name });
db.SaveChanges();
return CreatedAtRoute("DefaultApi", new { id = post.Tags.First().Id }, post);
}

Add/Update list in database using Entity framework 6

I have three tables QuestionBank,Question and Answer. " QuestionBank " will have list of Question and " Question " will have list of " Answer ".
QUESTIONBANK :-
public class QuestionBank
{
public int id { get; set; }
public string Text { get; set; }
public string Chapter { get; set; }
public string Standard { get; set; }
public List<Question> Question { get; set; }
public QuestionBank()
{
this.Question = new List<Question>();
}
}
QUESTION :-
public class Question
{
public int id { get; set; }
public string Title { get; set; }
public string QuestionText { get; set; }
public List<Answer> Answer { get; set; }
public string CorrectAnswer { get; set; }
public Question()
{
this.Answer = new List<Answer>();
}
}
ANSWER :-
public class Answer
{
public int id { get; set; }
public string AnswerText { get; set; }
}
WEB API :- //Edited
private IRepository<QuestionBank> _QuestionBankRepository;
public QuestionController(IRepository<QuestionBank> QuestionBankRepository)
{
_QuestionBankRepository = QuestionBankRepository;
}
[HttpPost]
[Route("Ques/Add")]
public Boolean Add(QuestionBank AddQuetionBankData)
{
var isQuetionBankPresent = _QuestionBankRepository.GetAll(p => p.Text == AddQuetionBankData.Text && p.Standard == AddQuetionBankData.Standard && p.Chapter == AddQuetionBankData.Chapter).FirstOrDefault<QuestionBank>();
if (isQuetionBankPresent != null)
{
/* Add the data in Question and Answer tables */
return false;
}
else
{
/* Add the data in all three tables */
return true;
}
}
I have this database for the web api. Now I want to add the data in database through json { "QuestionBank": QuestionBank, "Question": Question, "Answer": Answer } if the row is present in QuestionBank i dont want to add that data in QuestionBank table and only add the data in Question and Answer table with respective foreign keys. I am using the entity frame work and mvc 5 web api. I am stuck at this point. Please if any thing is needed let me know. Thanks in advance.
The Entity Framework way to update is to to Context.Entry([your object]).State = System.Data.Entity.EntityState.Modified; providing that the object is of the right type.
public Boolean Add(QuestionBank AddQuetionBankData)
{
bool flag = false;
var question = this.MapToQuestion(AddQuetionBankData); //map the input to the EF Type Question
var anwer = this.MapToAnswer(AddQuetionBankData); //map the input to the EF Type Answer
var isQuetionBankPresent = _QuestionBankRepository.GetAll(p => p.Text == AddQuetionBankData.Text && p.Standard == AddQuetionBankData.Standard && p.Chapter == AddQuetionBankData.Chapter).FirstOrDefault<QuestionBank>();
if (isQuetionBankPresent != null)
{
_context.Entry(question).State = EntityState.Modified;
_context.Entry(answer).State = EntityState.Modified;
/* Add the data in Question and Answer tables */
flag = false;
}
else
{
_context.Entry(question).State = EntityState.Modified;
_context.Entry(answer).State = EntityState.Modified;
_context.Entry(AddQuetionBankData).State = EntityState.Modified;
/* Add the data in all three tables */
flag = true;
}
_context.SaveChanges();
return flag;
}
private Question MapTo Question(QuestionBank q) //do this for Answers too
{
var _q = _context.Question.Where(a=>a.Id == q.Id).SingleOrDefault();
if(_q!=null)
{
_q.id = q.id; //this is already true
_q.Title = q.Title;
_q.QuestionText = q.Standard; //I guess
}
return _q;
}
The EF updates the Entity (the class you pass to the method Entry()) accordingly to its Type.
Notice that the position of the SaveChanges(): it works like a stored procedure, you do all the updates and the SaveChanges() is like the SQL COMMIT command.
You should also wrap the SaveChanges in a try/catch to handle errors, and dispose the _context.
EDIT
This class has as dependency IRepository<Question>, IRepository<QuestionBank>, and IRepository<Answer>.
You should create an UpdateController(or PublishController or whatever) that gets the three dependencies in the constructor (better a Facade Service), and call the Add() method for each one of them.
If you access directly the raw Database object you could do like I did and use the Entry() method for each table.

The entity or complex type cannot be constructed in a LINQ to Entities query [duplicate]

This question already has answers here:
The entity cannot be constructed in a LINQ to Entities query
(14 answers)
Closed 10 years ago.
I have two functions that look exactly the same except they create lists of two different objects. The two different objects look very much alike, but when I try to run one of the functions on one of the objects, I get the error message, "The entity or complex type cannot be constructed in a LINQ to Entities query.". Can someone explain to me what is happening in very simple terms? Also, can you tell me how to change my code so that it works? Thanks, Allan.
Function 1 (works):
public static List<ChartApp> ListChartApplications()
{
using (var db = new LatencyDBContext())
{
var appNames = db.LoginApplications.Select(item => new ChartApp()
{
LoginApplicationID = item.LoginApplicationID,
LoginAppName = item.LoginAppName,
}).OrderBy(item => item.LoginAppName);
return appNames.ToList();
}
}
Function 2 (throws error on "return appNames.ToList();"):
public static List<LoginApplication> ListApplications()
{
using (var db = new LatencyDBContext())
{
var appNames = db.LoginApplications.Select(item => new LoginApplication()
{
LoginApplicationID = item.LoginApplicationID,
LoginAppName = item.LoginAppName,
}).OrderBy(item => item.LoginAppName);
return appNames.ToList();
}
}
Classes:
public class ChartApp
{
public ChartApp()
{
this.LoginHistories = new List<ChartHist>();
}
public int? LoginApplicationID { get; set; }
public string LoginAppName { get; set; }
public virtual ICollection<ChartHist> LoginHistories { get; set; }
public int Step { get; set; }
}
public class LoginApplication
{
public LoginApplication()
{
this.LoginHistories = new List<LoginHistory>();
}
public int LoginApplicationID { get; set; }
public string LoginAppName { get; set; }
public virtual ICollection<LoginHistory> LoginHistories { get; set; }
}
Edit: Could the difference possibly be that one of the objects are mapped to the database?
public class LoginApplicationMap : EntityTypeConfiguration<LoginApplication>
{
public LoginApplicationMap()
{
// Primary Key
this.HasKey(t => t.LoginApplicationID);
// Properties
this.Property(t => t.LoginAppName)
.HasMaxLength(500);
// Table & Column Mappings
this.ToTable("LoginApplication");
this.Property(t => t.LoginApplicationID).HasColumnName("LoginApplicationID");
this.Property(t => t.LoginAppName).HasColumnName("LoginAppName");
}
}
My solution in this case was to just delete the non-working function and use the working one in all places. For, similar functions that are mapped, I use the following function to return values.
public static List<LoginEnvironment> ListEnvironments(bool allSelection)
{
using (var db = new LatencyDBContext())
{
//GET ALL THE ENVIRONMENT NAMES
var envNames = from e in db.LoginEnvironments
orderby e.LoginEnvName
select e;
//PUT ALL THE ENVIRONMENTS INTO A LOCAL LIST
var listEnv = new List<LoginEnvironment>();
if (allSelection)
{
var defaultAll = new LoginEnvironment();
defaultAll.LoginEnvironmentID = 0;
defaultAll.LoginEnvName = "All";
listEnv.Add(defaultAll);
}
foreach (var item in envNames)
{
var localEnv = new LoginEnvironment();
localEnv.LoginEnvironmentID = item.LoginEnvironmentID;
localEnv.LoginEnvName = item.LoginEnvName;
listEnv.Add(localEnv);
}
return listEnv;
}
}