UpdateDefinition with multiple fields not working - c#

I try to build an update request using the C# mongodb driver.
I would like to update a field only if he is not null. Here is the following code :
public void Replace(Core.Entity.Project project)
{
var filter = Builders<Entity.Project>.Filter.Eq(x => x.ProjectNumber, project.ProjectNumber);
var update = BuildUpdateRequest(project);
_mongoCollection.UpdateOne(filter, update);
}
private UpdateDefinition<Entity.Project> BuildUpdateRequest(Core.Entity.Project project)
{
var builder = Builders<Entity.Project>.Update;
var update = builder.Set(x => x.LastUpdateDate, DateTime.UtcNow);
if (!string.IsNullOrEmpty(project.UserId))
{
update.Set(x => x.UserId, project.UserId);
}
}
Unfortunately when I run my unit/integration tests, the data is not updated.
Do you have a better way to use this objects ?

Ok it was easy. Fixed it with :
update = update.Set(x => x.UserId, project.UserId);

Related

How to use AvroSerializer without a schema registry

I am trying to write a unit test that verifies that adding a new property to an Avro schema is backwards compatible.
First I took the Avro generated .cs model and saved it as MyModelOld.cs and renamed the class inside to MyModelOld.
Then I re-ran Avro gen against the avsc file with the new property.
What I'm trying to do is this:
var schemaRegistry = -> something that doesn't require a running docker image <-;
var deserializerOld = new AvroDeserializer<MyModelOld>(schemaRegistry);
var serializerNew = new AvroSerializer<MyModel>(schemaRegistry);
var myModel = new MyModel() {...};
var myModelBytes = await serializerNew.SerializeAsync(myModel, new());
var myModelOld = await deserializerOld.DeserializeAsync(myModelBytes, false, new());
// Check properties...
Then I was going to go the opposite direction and check that the new property uses the specified default value.
The problem I'm having is what to use for the schema registry. I don't want to have a docker image running for these tests because I don't think it shouldn't be necessary.
I've tried a mock of ISchemaRegistry, but it appears to need a fully functional class in order for serialize/deserialize to work.
I could probably walk through the logic for CachedSchemaRegistryClient and try to munge it to work, but before I do so I'd like to find out if someone knows of an ISchemaRegistry implementaion that would work for my use case.
Has anyone tried to write tests to validate backwards compatibility of Avro schema updates?
If so, how did you go about doing so?
Thanks.
I ended up doing it this way:
private ISchemaRegistryClient NewTestRegistry(string topic)
{
// Code to mock SchemaRegistry taken from:
// https://github.com/confluentinc/confluent-kafka-dotnet/blob/master/test/Confluent.SchemaRegistry.Serdes.UnitTests/SerializeDeserialize.cs
Dictionary<string, int> store = new Dictionary<string, int>();
var schemaRegistryMock = new Mock<ISchemaRegistryClient>();
#pragma warning disable CS0618 // Type or member is obsolete
schemaRegistryMock.Setup(x => x.ConstructValueSubjectName(topic, It.IsAny<string>()))
.Returns($"{topic}-value");
schemaRegistryMock.Setup(x => x.RegisterSchemaAsync($"{topic}-value", It.IsAny<string>(), It.IsAny<bool>()))
.ReturnsAsync((string topic, string schema, bool normalize) =>
store.TryGetValue(schema, out int id) ? id : store[schema] = store.Count + 1
);
#pragma warning restore CS0618 // Type or member is obsolete
schemaRegistryMock.Setup(x => x.GetSchemaAsync(It.IsAny<int>(), It.IsAny<string>()))
.ReturnsAsync((int id, string format) =>
new Schema(store.Where(x => x.Value == id).First().Key, null, SchemaType.Avro)
);
return schemaRegistryMock.Object;
}
[TestMethod]
public async Task BackwardsCompatible()
{
var topic = "MyCoolTopic";
var schemaRegistry = NewTestRegistry(topic);
var context = new SerializationContext(MessageComponentType.Value, topic);
var deserializerOld = new AvroDeserializer<MyModelOld>(schemaRegistry);
var serializerNew = new AvroSerializer<MyModel>(schemaRegistry);
var myModel = new MyModel() { /* Set properties */};
var myModelBytes = await serializerNew.SerializeAsync(myModel, context);
var myModelOld = await deserializerOld.DeserializeAsync(myModelBytes, false, context);
// Check properties...
}
[TestMethod]
public async Task ForwardsCompatible()
{
// Similar to the above test.
}
If you want to test schemas, you don't need Kafka-related serializers; just use raw Avro C# library.
Alternatively, look at the existing tests
var config = new SchemaRegistryConfig { Url = "irrelevanthost:8081" };
var src = new CachedSchemaRegistryClient(config);
Assert...(src... );

Elasticsearch NEST Suggester parser(s) not found

I'm attempting to utilize Elasticsearch's 'Suggester' functionality.
Using Phrase, Term, or Completion I always get the following error variation.
unable to parse SuggestionBuilder with name [COMPLETION]: parser not found"
unable to parse SuggestionBuilder with name [TERM]: parser not found"
unable to parse SuggestionBuilder with name [PHRASE]: parser not found"
I have tried multiple 6.x NEST versions and they all have the same issue.
Upgrading to 7.0alpha1 does change the error, but seems to cause a myriad of other issues, and I rather not use an alpha library in production.
I'm currently following this tutorial and working it into my existing code: https://github.com/elastic/elasticsearch-net-example/tree/6.x-codecomplete-netcore#part-6-suggestions
Currently using NEST 6.1
Model:
public class SearchResult {
public SearchResult()
{
TitleSuggest = new CompletionField {Input = new List<string>(Title.Split(' '))};
}
public CompletionField TitleSuggest { get; set; }
//etc
}
Index Method:
public async Task<IActionResult> CreateIndex()
{
await _searchClient.CreateIndexAsync(SearchIndexName, indexSelector =>
indexSelector
.Mappings(mappingsDescriptor =>
mappingsDescriptor.Map<Models.SearchResult>(y => y.AutoMap().Properties(pr=>pr.Completion(c => c.Name(p => p.TitleSuggest)
))))
Suggest Method:
public async Task<ISearchResponse<SearchResult>> Suggest(string keyword)
{
return await _searchClient.SearchAsync<SearchResult>(
s =>
s.Suggest(ss => ss
.Completion("title", cs => cs
.Field(f => f.TitleSuggest)
.Prefix(keyword)
.Fuzzy(f => f
.Fuzziness(Fuzziness.Auto)
)
.Size(5))
}
I'm having a hard time deciphering the error.
It seems as though the NEST libraries are missing the Suggester parsers?
Any help would be great, thanks!
As a follow up, #RussCam answered my question here
I had a ConnectionSetting (DefaultFieldNameInferrer) that was uppercasing my suggester
private IElasticClient ElasticClient(IConfiguration _config, string defaultIndex)
{
var settings = new ConnectionSettings(new Uri(_config.GetSection("Search:Url").Value))
.BasicAuthentication(_config.GetSection("Search:User").Value, _config.GetSection("Search:Password").Value)
.DefaultIndex(_config.GetSection(defaultIndex).Value);
//settings.DefaultFieldNameInferrer(p => p.ToUpper(CultureInfo.CurrentCulture));
//Enable ElasticSearch Debugging
settings.PrettyJson().DisableDirectStreaming();
return new ElasticClient(settings);
}
Try this:
var searchResponse = await _searchClient.SearchAsync<SearchResult>(s => s
.Index(ConfigurationManager.AppSettings.Get("index"))
.Type(ConfigurationManager.AppSettings.Get("indextype"))
.Suggest(su => su
.Completion("suggest", cs => cs
.Size(20)
.Field(f => f.TitleSuggest)
.Fuzzy(f => f
.Fuzziness(Fuzziness.Auto))
.Size(5))));

Tests using InMemoryDatabase and Identity columns, how to deal?

.Net Core 2.2 / EFC 2.2.3 / Pomelo.EntityFrameworkCore.MySql 2.2.0
Imagine that you have a table called Colors with some predefined data.
public void Configure(EntityTypeBuilder<Color> builder)
{
builder.ToTable("Colors");
builder.HasKey(r => r.Id).UseMySqlIdentityColumn();
builder.Property(r => r.Name).IsRequired().HasMaxLength(255);
builder.Property(v => v.RGB).IsRequired().HasMaxLength(7);
builder.HasData(GetSeed());
}
private ICollection<Color> GetSeed()
{
return new List<Color>()
{
new Color(){Id=1, Name="Black", RGB="#000"},
new Color(){Id=2, Name="White", RGB="#fff"},
}
}
One of my tests is to test the CreateColorCommandHandler. Very straightfoward
var Context = CBERPContextFactory.Create();
var query = new CreateColorCommandHandler(Context);
var command = new CreateColorCommand();
command.Name= "Random color";
command.RGB = "#001122";
var colorId = await query.Handle(command, CancellationToken.None);
//Assert
Assert.IsInstanceOf<long>(colorId);
Assert.NotZero(colorId);
var cor = Context.Colors.Where(p => p.Id == colorId).SingleOrDefault();
Assert.NotNull(cor);
Assert.AreEqual(command.Name, cor.Name);
Assert.AreEqual(command.RGB, cor.RGB);
CBERPContextFactory.Destroy(Context);
//>>> Handle simply add a new entity without informing ID
Handle method
public async Task<long> Handle(CreateColorCommand request, CancellationToken cancellationToken)
{
var entity = new Color
{
Name = request.Name,
RGB = request.RGB,
};
_context.Colors.Add(entity);
await _context.SaveChangesAsync(cancellationToken);
return entity.Id;
}
When I ran this test I get the error An item with the same key has already been added. Key: 1. Which means that InMemoryDatabase do not has auto increment feature.
Am I writing the test wrong?
How can I test case like this? I want to make sure that the command is OK.
Probably I am missing some very basic rule here.
I assume problem is in the following line:
var Context = CBERPContextFactory.Create();
May be you are using the same context instance for multiple tests. According to Testing with InMemory documentation:
Each test method specifies a unique database name, meaning each method has its own InMemory database.
So make sure that your each and every test method has a distinct context instance.
If still does not work then try setting the identity key value manually because InMemory database may does not support auto-increment.
InMemoryDatabase do not have all features yet, and AUTO INCREMENT one of those that need improvements: https://github.com/aspnet/EntityFrameworkCore/issues/6872
Not the answer I wanted, but is the one working for now: clear all seeds before testing.
private static void Clear(this DbContext context)
{
var properties = context.GetType().GetProperties();
foreach (var property in properties)
{
var setType = property.PropertyType;
bool isDbSet = setType.IsGenericType && (typeof(DbSet<>).IsAssignableFrom(setType.GetGenericTypeDefinition()));
if (!isDbSet) continue;
dynamic dbSet = property.GetValue(context, null);
dbSet.RemoveRange(dbSet);
}
context.SaveChanges();
}

Hangfire duplicates jobs on server restart

So we have an .NET Core API which uses hangfire as a task scheduler.
On startup, our API starts the following functions :
public void CreateTasks()
{
/* DATABASE TASKS */
SyncDatabaseTask();
/* SMS TASKS */
SendSmsTask();
}
public void SendSmsTask()
{
var taskId = BackgroundJob.Schedule(() => _smsService.SendSms(), TimeSpan.FromMinutes(30));
BackgroundJob.ContinueWith(taskId, () => SendSmsTask());
}
This creates the job SendSmsTask in Hangfire on startup and does not start a second job until the first one has been completed.
The issue that we just noticed however is that whenever our API reboots (server update for example) the existing jobs are still running and news jobs are being added.
So we would like to remove all scheduled or running jobs on startup.
I've looked through the documentation (http://docs.hangfire.io/en/latest/) but couldn't really find a solution for this issue.
This should solve your problem, just note that this is untested.
private void RemoveAllHangfireJobs()
{
var hangfireMonitor = JobStorage.Current.GetMonitoringApi();
//RecurringJobs
JobStorage.Current.GetConnection().GetRecurringJobs().ForEach(xx => BackgroundJob.Delete(xx.Id));
//ProcessingJobs
hangfireMonitor.ProcessingJobs(0, int.MaxValue).ForEach(xx => BackgroundJob.Delete(xx.Key));
//ScheduledJobs
hangfireMonitor.ScheduledJobs(0, int.MaxValue).ForEach(xx => BackgroundJob.Delete(xx.Key));
//EnqueuedJobs
hangfireMonitor.Queues().ToList().ForEach(xx => hangfireMonitor.EnqueuedJobs(xx.Name,0, int.MaxValue).ForEach(x => BackgroundJob.Delete(x.Key)));
}
If you still interest Pieter Alberts solution.
Some little changes on that.
If you use old code and you have old job in db, you will get Format Exception.
In //RecurringJobs section you have to change line like this:
JobStorage.Current.GetConnection().GetRecurringJobs().ForEach(xx => RecurringJob.RemoveIfExists(xx.Id));
TL;DR
Old Code:
private void RemoveAllHangfireJobs()
{
var hangfireMonitor = JobStorage.Current.GetMonitoringApi();
//RecurringJobs
JobStorage.Current.GetConnection().GetRecurringJobs().ForEach(xx => BackgroundJob.Delete(xx.Id));
//ProcessingJobs
hangfireMonitor.ProcessingJobs(0, int.MaxValue).ForEach(xx => BackgroundJob.Delete(xx.Key));
//ScheduledJobs
hangfireMonitor.ScheduledJobs(0, int.MaxValue).ForEach(xx => BackgroundJob.Delete(xx.Key));
//EnqueuedJobs
hangfireMonitor.Queues().ToList().ForEach(xx => hangfireMonitor.EnqueuedJobs(xx.Name,0, int.MaxValue).ForEach(x => BackgroundJob.Delete(x.Key)));
}
New Code:
private void RemoveAllHangfireJobs()
{
var hangfireMonitor = JobStorage.Current.GetMonitoringApi();
//RecurringJobs
JobStorage.Current.GetConnection().GetRecurringJobs().ForEach(xx => RecurringJob.RemoveIfExists(xx.Id)); // this line changed!
//ProcessingJobs
hangfireMonitor.ProcessingJobs(0, int.MaxValue).ForEach(xx => BackgroundJob.Delete(xx.Key));
//ScheduledJobs
hangfireMonitor.ScheduledJobs(0, int.MaxValue).ForEach(xx => BackgroundJob.Delete(xx.Key));
//EnqueuedJobs
hangfireMonitor.Queues().ToList().ForEach(xx => hangfireMonitor.EnqueuedJobs(xx.Name,0, int.MaxValue).ForEach(x => BackgroundJob.Delete(x.Key)));
}
PS Edit:
My Hangfire version is 1.7.9
and using Hangfire.PostgreSql
//Start Hangfire Server
var varJobOptions = new BackgroundJobServerOptions();
varJobOptions.ServerName = "job.fiscal.io";
varJobOptions.WorkerCount = Environment.ProcessorCount * 10;
app.UseHangfireServer(varJobOptions);
app.UseHangfireDashboard("/jobs", new DashboardOptions {
Authorization = new[] { new clsHangFireAuthFilter() }
});
//Remove Duplicte HangFire Server
var varMonitoringApi = JobStorage.Current.GetMonitoringApi();
var varServerList = varMonitoringApi.Servers().Where(r => r.Name.Contains("job.fiscal.io"));
foreach( var varServerItem in varServerList) {
JobStorage.Current.GetConnection().RemoveServer(varServerItem.Name);
}
HF 1.7.28
For me deleting Enqueued jobs like suggested did not work.
Instead, I had to use the following:
hangfireMonitor.Queues().ToList().ForEach(x => x.FirstJobs.Where(j => j.Value.InEnqueuedState).ToList().ForEach(x => BackgroundJob.Delete(x.Key)));

How to change the dependency at runtime using simple injector

I am new to simple injector. I have data access layer that has dependency on Force Client. I have register the ForceClient dependency. I want to replace the default value of ForceClient once user login into the application.
Please let me know, how i can change the default values at run time.
Ioc.ServiceContainer.Register(() => new ForceClient(
"test",
"test",
"test"));
Here is the complete detail about the requirement. I have DAL in our Xamarin project that retrieve data from sales force using Developerforce.Force apis. I am writing unit test cases using MOQ to test the DAL.
DAL Code.
public CustomerRepository(IForceClient client)
{
_client = client;
}
public async Task<long> GetTotalContacts()
{
string totalContactCountQry = "some query"
var customerCount = await _client.QueryAsync<ContactsTotal>(totalContactCountQry);
var firstOrDefault = customerCount.Records.FirstOrDefault();
return firstOrDefault != null ? firstOrDefault.Total : 0;
}
Unit Test Case Code.
[SetUp]
public void Init()
{
forceClientMock = new Mock<IForceClient>();
forceClientMock.Setup(x => x.ForceClient(It.IsAny<string>(),
It.IsAny<string>(), It.IsAny<string>(), It.IsAny<HttpClient>()))
.Return(new ForceClient(It.IsAny<string>(), It.IsAny<string>(),
It.IsAny<string>(), It.IsAny<HttpClient>()));
forceClientMock.Setup(x => x.QueryAsync<ContactsTotal>(It.IsAny<string>()))
.ReturnsAsync(new QueryResult<ContactsTotal>());
forceClientMock.Setup(x => x.QueryAsync<ContactsTotal>(It.IsAny<string>()))
.ReturnsAsync(new QueryResult<ContactsTotal>() { Records=new List<ContactsTotal>() });
}
[Test]
public void GetTotalContacts()
{
ICustomerRepository customerRepostory = new CustomerRepository(forceClientMock.Object);
Assert.AreEqual(customerRepostory.GetTotalContacts().Result,0);
}
Simple Injector Registry on application initialization
container.Register<IForceClient>((() => new ForceClient(
UserState.Current.ApiBaseUrl,
UserState.Current.AuthToken.AccessToken,
UserState.Current.ApiVersion)), Lifestyle.Transient);
The instance of ForceClient that i am creating during the registry is being created with all default valued of UserState. The actual value gets assigned once user login into the application.
I except ForceClient instance to have the updated value after login to access the sales force to retrieve the data but the program is giving error on below line DAL
var customerCount = await _client.QueryAsync<ContactsTotal>(totalContactCountQry);
The reason is that the forceClient still contain default values. How can i make sure that the FoceClient instance get created after login to use the actual value of UserState
You can accomplish what you want by using Func<T>.
Rather than IForceClient in your classe, you can inject a Func<IForceClient> :
public CustomerRepository(Func<IForceClient> clientFunc)
{
_clientFunc = clientFunc;
}
public async Task<long> GetTotalContacts()
{
string totalContactCountQry = "some query"
// calling _clientFunc() will provide you a new instance of IForceClient
var customerCount = await _clientFunc().QueryAsync<ContactsTotal>(totalContactCountQry);
var firstOrDefault = customerCount.Records.FirstOrDefault();
return firstOrDefault != null ? firstOrDefault.Total : 0;
}
The simple injector registration:
// Your function
Func<IForceClient> fonceClientFunc = () => new ForceClient(
UserState.Current.ApiBaseUrl,
UserState.Current.AuthToken.AccessToken,
UserState.Current.ApiVersion);
// the registration
container.Register<Func<IForceClient>>( () => fonceClientFunc, Lifestyle.Transient);

Categories