I have a simple ASP.NET Core 2.2 Controller action that returns incomplete JSON even when sent a request from POSTMAN.
The code runs fine until I include the navigation property for related entity "Books". The results are returned correctly from the ToList() method on the IQueryable object, as I can see when debugging. But, for some reason, when the ReadAsStringAsync() runs, it returns only part of the expected JSON result.
Below is the code for the API:
[HttpPost]
[Route("api/TestEntity/ListTest")]
public async Task<ActionResult<IEnumerable<TestEntityPerson>>> ListTest()
{
var query = _context.Persons
.Include(p => p.Books)
.AsQueryable().Take(5);
var results = await Task.FromResult(Json(query.ToList()));
return results;
}
And the result I receive (which is only a part of the expected result) in the client is:
[{"$type":"Identica.My.Bonus.Entities.TestEntityPerson, Identica.My.Bonus","name":"Susan","balance":240749.08345506949,"age":56,"books":[{"$type":"Identica.My.Bonus.Entities.TestEntityBook, Identica.My.Bonus","title":"SRWZLSRKQNYKPY","author":"VEJZP","price":13.334878714911119,"personId":"f24dbe36-1f99-4a59-3cb7-08d6c4048ace"
Any pointers on what I could try to solve this problem? I couldn't find any relevant questions on stack overflow.
EDIT: This happens only when there is a navigational property on the related entity pointing back at the original entity. When I removed this property, the problem was gone.
And this is the client code:
class Program
{
static async Task Main(string[] args)
{
Console.WriteLine("Press any key to run query...");
Console.Read();
MediaTypeWithQualityHeaderValue mediaTypeJson = new MediaTypeWithQualityHeaderValue("application/json");
List<MediaTypeFormatter> formatters = new List<MediaTypeFormatter> {
new JsonMediaTypeFormatter {
SerializerSettings = new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Objects
}
}
};
using (HttpClient httpClient = new HttpClient() { BaseAddress = new Uri("https://localhost:44359/") })
{
httpClient.DefaultRequestHeaders.Accept.Clear();
httpClient.DefaultRequestHeaders.Accept.Add(mediaTypeJson);
Expression<Func<TestEntityPerson, bool>> expressionFilter =
t => (t.Balance > 240000 || t.Age < 25) &&
(t.Books == null || t.Books.Any(b => b.Price < 7 || b.Title.StartsWith('A')));
var filterNode = expressionFilter.ToExpressionNode();
Expression<Func<IQueryable<TestEntityPerson>, IOrderedQueryable<TestEntityPerson>>> expressionOrderBy = t => t.OrderByDescending(x => x.Balance);
var orderByNode = expressionOrderBy.ToExpressionNode();
Pagination pagination = new Pagination() { Start = 0, Limit = 10 };
QueryOptionsNodes queryOptions = new QueryOptionsNodes()
{
FilterExpressionNode = filterNode,
SortingExpressionNode = orderByNode,
Pagination = pagination
};
try
{
Console.WriteLine("Sending request...");
var response = await httpClient.PostAsync("api/TestEntity/List", queryOptions, formatters[0], mediaTypeJson, CancellationToken.None);
response.EnsureSuccessStatusCode();
Console.WriteLine("Reading Response request...");
var result = await response.Content.ReadAsAsync<IEnumerable<TestEntityPerson>>(formatters, CancellationToken.None);
ShowEntities(result);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
try
{
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
Console.WriteLine("Press any key to exit...");
Console.ReadLine();
Console.ReadLine();
}
private static void ShowEntities(IEnumerable<TestEntityPerson> testEntities)
{
foreach (var entity in testEntities)
{
Console.WriteLine("{0}) {1} Age = {2} Balance = {3}", entity.Id, entity.Name, entity.Age, entity.Balance);
if (entity.Books != null)
{
Console.WriteLine("Books:");
foreach (var book in entity.Books)
{
Console.WriteLine("-- {0}) {1} Price = {2}", book.Id, book.Title, book.Price);
}
}
Console.WriteLine();
}
}
}
The problem that there was an unnecessary navigational property on the "related entity" pointing back at the "original entity".
public class TestEntityPerson : BaseEntity<int>
{
public string Name { get; set; }
public double Balance { get; set; }
public int Age { get; set; }
public List<TestEntityBook> Books { get; set; }
}
And the related entity:
public class TestEntityBook : BaseEntity<int>
{
public string Title { get; set; }
public string Author { get; set; }
public double Price { get; set; }
public Guid TestEntityPersonId { get; set; }
// The problematic property
public TestEntityPerson TestEntityPerson { get; set; }
}
When I removed the TestEntityPerson property from the Book entity, the problem was solved.
EDIT: I suppose this is not a real solution, since it should be OK to have inverse navigation properties, but in this case it was causing a problem.
Related
I have a Topic class:
public class Topic : BaseEntity
{
public string Name { get; set; }
public bool Active { get; set; }
public IList<Content>? Contents { get; set; }
}
And a Content class:
public class Content : BaseEntity
{
public string Name { get; set; }
public string URL { get; set; }
public string StartingVersion { get; set; }
public string EndingVersion { get; set; }
public string Summary { get; set; }
public bool Active { get; set; }
public IList<Topic> Topics { get; set; }
}
The BaseEntity looks like this:
public class BaseEntity
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public int CreatedBy { get; set; }
public DateTime CreatedDate { get; set; }
public int ModifiedBy { get; set; }
public DateTime ModifiedDate { get; set; }
}
My DataContext looks like this:
public DataContext(DbContextOptions<DataContext> options) : base(options) { }
private DbSet<Topic> Topics { get; set; }
private DbSet<Content> Contents { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
}
And I'm trying to use a generic Repository. The saveEntity looks like this:
public async Task<T> SetEntity<T>(T entity) where T : BaseEntity
{
using (var scope = _serviceProvider.CreateScope())
{
entity.CreatedDate = DateTime.Now;
var _dbContext = scope.ServiceProvider.GetRequiredService<DataContext>();
_dbContext.Add(entity);
await _dbContext.SaveChangesAsync();
return entity;
}
}
And the Content Service method that does the creation of Contents looks like this:
public async Task<ContentDTO> AddContentAsync(ContentDTO content)
{
_modelHelper.ModelValidation(content);
await Checks(content, false);
foreach (var item in content.Topics)
{
Expression<Func<Topic, bool>> expTopic = i => i.Id == item.Id && i.Active == true;
var topic = await _dataRepository.GetEntityAsync(expTopic);
if (topic == null)
{
throw new KeyNotFoundException($"Topic with ID {item.Id} not found");
}
}
Content toSaveContent = Mapping.Mapper.Map<Content>(content);
toSaveContent.Active = true;
Content newContent = await _dataRepository.SetEntity(toSaveContent);
return Mapping.Mapper.Map<ContentDTO>(newContent);
}
My problem is that when I try to create a new Content EF fails to detect that the Topics included in the body of the Content are existing ones and tries to add them as new in the DB. Obviously, this raises a SQL exception saying I can't define the Id of the Topic.
What I'm missing??
Thank you for your help
EDIT:
Also tried to retrieve the Topics from context, but didn't work either:
public async Task<ContentDTO> AddContentAsync(ContentDTO content)
{
Expression<Func<Content, bool>> exp = i => i.URL == content.URL && i.Active == true;
if (_dataRepository.GetEntities(exp).Any())
{
throw new DuplicateWaitObjectException("Object already exist");
}
CheckObjectives(content.Objectives);
Content toSaveContent = Mapping.Mapper.Map<Content>(content);
_modelHelper.ModelValidation(toSaveContent);
toSaveContent.Active = true;
toSaveContent.Topics = new List<Topic>();
foreach (var item in content.Topics)
{
Expression<Func<Topic, bool>> expTopic = i => i.Id == item.Id && i.Active == true;
var topic = await _dataRepository.GetEntity(expTopic);
if(topic == null)
{
throw new KeyNotFoundException($"Topic with ID {item.Id} not found");
}
toSaveContent.Topics.Add(topic);
}
Content newContent = await _dataRepository.SetEntity(toSaveContent);
return Mapping.Mapper.Map<ContentDTO>(newContent);
}
EDIT2:
You are right, Guru Stron, I'll extract the GetEntity from the foreach and just take them all before.
This is my GetEntity method in the generic repository:
public async Task<T> GetEntity<T>(Expression<Func<T, bool>> predicate) where T : BaseEntity
{
using (var scope = _serviceProvider.CreateScope())
{
var _dbContext = scope.ServiceProvider.GetRequiredService<DataContext>();
return _dbContext.Set<T>().Where(predicate).FirstOrDefault();
}
}
EDIT3:
I'm sorry for the long delay. I'm not sure if this is a context issue. When I try to save a Content with the following JSON:
{
"name": "Style",
"url": "https://player.vimeo.com/video/41513143?h=6215248d63",
"startingVersion": "3.10.1",
"endingVersion": "3.10.1",
"summary": "This is a very nice content",
"topics": [
{
"id": 2,
"name": "NewTopic"
}
],
"objectives": [
{
"id": 1,
"index": 1,
"description": "This is a nice Objective"
}
]
}
I can see in the saving method of the repository that the Topic with ID 2 indeed exists:
It looks like the object Topic with Id 2 exists in the context but EF can't find it??
EDIT4: Edited for clarity
EDIT 5:
Tried to add the DataContext as Scoped in the ServiceCollection, and inject it in the Repository:
public static IServiceCollection AddDependencyInjectionConfiguration(this IServiceCollection services)
{
services.AddScoped<IDataRepository, DataRepository>();
services.AddScoped<DataContext>();
[...]
}
Used DI in the Repository and removed Scopes for using the DataContext:
[...]
public DataRepository(IServiceProvider serviceProvider, IHttpContextAccessor contextAccesor, DataContext dataContext)
{
_serviceProvider = serviceProvider;
_httpContextAccessor = contextAccesor;
_dbContext = dataContext;
}
[...]
public async Task<T> SetEntity<T>(T entity) where T : BaseEntity
{
entity.CreatedDate = DateTime.UtcNow;
entity.CreatedBy = _currentUserId;
_dbContext.Add(entity);
await _dbContext.SaveChangesAsync();
return entity;
}
[...]
And removed the Topic search in the service method to avoid the exception of "object already use in reading operation"
public async Task<ContentDTO> AddContentAsync(ContentDTO content)
{
_modelHelper.ModelValidation(content);
await Checks(content, false);
Content toSaveContent = Mapping.Mapper.Map<Content>(content);
toSaveContent.Active = true;
Content newContent = await _dataRepository.SetEntity(toSaveContent);
return Mapping.Mapper.Map<ContentDTO>(newContent);
}
But the result is still the same... EF is trying to save the Topic...
EDIT 6:
I tried to update Topics before saving the Content, but it is still trying to save the same Topic:
public async Task<ContentDTO> AddContentAsync(ContentDTO content)
{
await Checks(content, false);
Content toSaveContent = Mapping.Mapper.Map<Content>(content);
_modelHelper.ModelValidation(content);
toSaveContent.Active = true;
foreach (var item in content.Topics)
{
Topic? topic = await _dataRepository.GetEntityAsync<Topic>(x => x.Id == item.Id);
if (topic == null)
{
throw new KeyNotFoundException($"Topic with ID {item.Id} not found");
}
if (topic.Contents == null) {
topic.Contents = new List<Content>() { toSaveContent };
}
else {
topic.Contents.Add(toSaveContent);
}
await _dataRepository.UpdateEntityAsync(topic, topic.Id);
}
Content newContent = await _dataRepository.SetEntity(toSaveContent);
return Mapping.Mapper.Map<ContentDTO>(newContent);
}
EDIT 7:
As #rjs123431 suggested I cleared the Topics list of the Content object to save and stored the reference to the Content in the Topics and updated the objects.
public async Task<ContentDTO> AddContentAsync(ContentDTO content)
{
await Checks(content, false);
_modelHelper.ModelValidation(content);
Content toSaveContent = Mapping.Mapper.Map<Content>(content);
toSaveContent.Active = true;
toSaveContent.Topics = new List<Topic>();
List<Topic> topicsToSave = new List<Topic>();
foreach (var item in content.Topics)
{
Expression<Func<Topic, bool>> expTopic = i => i.Id == item.Id && i.Active == true;
var topic = await _dataRepository.GetEntityAsync(expTopic);
if (topic == null)
{
throw new KeyNotFoundException($"Topic with ID {item.Id} not found");
}else
{
if (topic.Contents == null)
topic.Contents = new List<Content>() { toSaveContent };
else
topic.Contents.Add(toSaveContent);
topicsToSave.Add(topic);
}
}
await _dataRepository.UpdateEntitiesAsync(topicsToSave);
Content newContent = await _dataRepository.SetEntity(toSaveContent);
return Mapping.Mapper.Map<ContentDTO>(newContent);
}
But with this code, the Content is saved, but in the ContentTopic table nothing is saved, therefore I lose the reference to the Topics.
EF Core uses concept of change tracking to manage data changes.
You should not create scope inside you generic repository (assuming you have default scoped context registration) - each scope will have it's own database context with it's own tracking, so the context which performs saving will have no idea about related entities and consider them as new ones (as you observe).
Usual approach is to have the outside control to control over scope, for example in ASP.NET Core the framework will create a scope on per request level and usually the dbcontext is shared on per request/scope basis.
So you need to remove the manual scope handling in the repository and use constructor injection so the repository shares the change tracking information between get and save queries, otherwise you will need to write some cumbersome code which will find and attach existing related entities in all the navigation properties of the saved entity.
Since you have a many-to-many relationship, and want to link your content to topics that are already save, you should not add topic to your content.
foreach (var item in content.Topics)
{
Expression<Func<Topic, bool>> expTopic = i => i.Id == item.Id && i.Active == true;
var topic = await _dataRepository.GetEntity(expTopic);
if(topic == null)
{
throw new KeyNotFoundException($"Topic with ID {item.Id} not found");
}
//toSaveContent.Topics.Add(topic); // no need for this line
}
Content newContent = await _dataRepository.SetEntity(toSaveContent);
Instead, after you save your content, loop through the topics and add the newly saved content to it and update the topic so content will be linked to that topic and vice versa.
Something like this:
foreach (var topic in content.Topics)
{
var topicEntity = await _topicRepository.GetAllIncluding(x => x.Contents)
.FirstOrDefaultAsync(x => x.Id == topic.Id);
if (topicEntity != null)
{
topicEntity.Contents.Add(content);
await _topicRepository.UpdateAsync(topicEntity);
}
}
Update 2:
You can even get the topic and add the content to it without having to save the content first. Content should have an empty topics of course.
I am developing an api, which has to return csv file on some endpoint. Here's my controller responsible for csv generation:
[ApiController]
[Route("api/[controller]")]
[Authorize]
public sealed class ReportController : BaseController
{
public ReportController(ICommandBus commandBus,
IQueryBus queryBus)
: base(commandBus, queryBus)
{
}
[HttpGet]
public async Task<IActionResult> GetReportAsync([FromQuery] GenerateReportRequest request)
{
try
{
var report = await QueryBus
.SendAsync<GenerateReportQuery, Report>(new GenerateReportQuery
{
Filters = request.Filters,
ResponseFileFormat = request.ResponseFileFormat,
WithPodOnly = request.WithPodOnly
});
return File(report.Content,
report.Type,
report.Name);
}
catch (Exception e)
{
// ToDo: Handle exception in proper way
return StatusCode(StatusCodes.Status500InternalServerError,
e.Message);
}
}
}
When the request comes to my api, certain handler is invoked, and the csv generation starts in CsvGenerationStrategy class, which is attached below:
public class CsvGenerationStrategy : IReportGenerationStrategy
{
public async Task<Report> GenerateReportAsync(ICollection<ShipmentEntity> shipmentEntities)
{
var shipment = shipmentEntities
.Select(s => (Shipment) s)
.ToList();
await using var memoryStream = new MemoryStream();
await using var streamWriter = new StreamWriter(memoryStream);
await using var csvWriter = new CsvWriter(streamWriter, CultureInfo.InvariantCulture);
csvWriter.Configuration.Delimiter = ";";
await csvWriter.WriteRecordsAsync(shipment);
var content = memoryStream.ToArray();
var report = new Report
{
Content = content,
Type = ReportConstants.CsvFileType,
Name = ReportConstants.CsvReportFileName
};
return report;
}
private class Shipment
{
[Name(ReportConstants.IssueColumnName)]
public string Issue { get; set; }
[Name(ReportConstants.MaterialReleaseReceiptColumnName)]
public string MaterialReleaseReceipt { get; set; }
[Name(ReportConstants.FreightBillIssueColumnName)]
public string FreightBillIssue { get; set; }
[Name(ReportConstants.InvoiceNumberColumnName)]
public string InvoiceNumber { get; set; }
[Name(ReportConstants.TaxCodeColumnName)]
public string TaxCode { get; set; }
[Name(ReportConstants.ContractorIdColumnName)]
public string ContractorId { get; set; }
[Name(ReportConstants.AddressIdColumnName)]
public string AddressId { get; set; }
[Name(ReportConstants.ContractorNameColumnName)]
public string ContractorName { get; set; }
[Name(ReportConstants.ShipmentCountryColumnName)]
public string ShipmentCountry { get; set; }
public static explicit operator Shipment(ShipmentEntity entity) =>
entity != null
? new Shipment
{
Issue = entity.Issue,
MaterialReleaseReceipt = entity.MaterialReleaseReceipt,
FreightBillIssue = entity.FreightBillIssue,
InvoiceNumber = entity.InvoiceNumber,
TaxCode = entity.TaxCode,
ContractorId = entity.ContractorId,
AddressId = entity.AddressId,
ContractorName = entity.ContractorName,
ShipmentCountry = entity.ShipmentCountry
}
: null;
}
}
The code looks properly, but the behavior of the class is quite strange. In most cases, the generation runs properly, but few times i have noticed a situation, when the MemoryStream object contains no data, even if shipment collection is correct. I believe, such a behavior does not depend on data passed as a parameter. Probably i've made something wrong with the streams. How to use them properly? How to generate csv file correctly using CsvHelper library?
I've found a solution. StreamWriter has to be flushed, after writing records, so now the function looks like:
public async Task<Report> GenerateReportAsync(ICollection<ShipmentEntity> shipmentEntities)
{
var shipment = shipmentEntities
.Select(s => (Shipment) s)
.ToList();
await using var memoryStream = new MemoryStream();
await using var streamWriter = new StreamWriter(memoryStream);
await using var csvWriter = new CsvWriter(streamWriter, CultureInfo.InvariantCulture);
csvWriter.Configuration.Delimiter = ";";
await csvWriter.WriteRecordsAsync(shipment);
await streamWriter.FlushAsync();
var report = new Report
{
Content = memoryStream.ToArray(),
Type = ReportConstants.CsvFileType,
Name = ReportConstants.CsvReportFileName
};
return report;
}
And it works properly :)
I have an Employee object, I'm trying to update a record (i.e., Update / Remove) using a multiple task (Parallel Execution) using single DB Entity Context. But I'm getting the following exception
Message = "Object reference not set to an instance of an object."
Consider the following DTO's
public class Employee
{
public int EmployeeId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public List<ContactPhone> ContactPhoneNumbers { get; set; }
public List<ContactEmail> ContactEmailAddress { get; set; }
}
public class ContactPhone
{
public int ContactId { get; set; }
public string Type { get; set; }
public string Number { get; set; }
}
public class ContactEmail
{
public int ContactId { get; set; }
public string Type { get; set; }
public string Number { get; set; }
}
Employee Table:
EmployeeId FirstName LastName
_________________________________
1 Bala Manigandan
ContactPhone Table:
ContactId EmployeeId Type Number
__________________________________________
1 1 Fax 9123456789
2 1 Mobile 9123456789
ContactPhone Table:
ContactId EmployeeId Type EmailAddress
______________________________________________
1 1 Private bala#gmail.com
2 1 Public bala#ymail.com
In-Coming API Object is
DTO.Employee emp = new DTO.Employee()
{
EmployeeId = 1,
FirstName = "Bala",
LastName = "Manigandan",
ContactPhoneNumbers = new List<DTO.ContactPhone>
{
new DTO.ContactPhone()
{
Type = "Mobile",
Number = "9000012345"
}
},
ContactEmailAddress = new List<DTO.ContactEmail>()
{
new DTO.ContactEmail()
{
Type = "Private",
EmailAddress = "bala#gmail.com"
},
new DTO.ContactEmail()
{
Type = "Public",
EmailAddress = "bala#ymail.com"
}
}
};
I'm getting an API request to update Mobile number and to remove the Fax number for a specified Employee.
Consider the task methods:
public void ProcessEmployee(DTO.Employee employee)
{
if(employee != null)
{
DevDBEntities dbContext = new DevDBEntities();
DbContextTransaction dbTransaction = dbContext.Database.BeginTransaction();
List<Task> taskList = new List<Task>();
List<bool> transactionStatus = new List<bool>();
try
{
Employee emp = dbContext.Employees.FirstOrDefault(m => m.EmployeeId == employee.EmployeeId);
if (emp != null)
{
Task task1 = Task.Factory.StartNew(() =>
{
bool flag = UpdateContactPhone(emp.EmployeeId, employee.ContactPhoneNumbers.FirstOrDefault().Type, employee.ContactPhoneNumbers.FirstOrDefault().Number, dbContext).Result;
transactionStatus.Add(flag);
});
taskList.Add(task1);
Task task2 = Task.Factory.StartNew(() =>
{
bool flag = RemoveContactPhone(emp.EmployeeId, "Fax", dbContext).Result;
transactionStatus.Add(flag);
});
taskList.Add(task2);
}
if(taskList.Any())
{
Task.WaitAll(taskList.ToArray());
}
}
catch
{
dbTransaction.Rollback();
}
finally
{
if(transactionStatus.Any(m => !m))
{
dbTransaction.Rollback();
}
else
{
dbTransaction.Commit();
}
dbTransaction.Dispose();
dbContext.Dispose();
}
}
}
public async Task<bool> UpdateContactPhone(int empId, string type, string newPhone, DevDBEntities dbContext)
{
bool flag = false;
try
{
var empPhone = dbContext.ContactPhones.FirstOrDefault(m => (m.EmployeeId == empId) && (m.Type == type));
if (empPhone != null)
{
empPhone.Number = newPhone;
await dbContext.SaveChangesAsync();
flag = true;
}
}
catch (Exception ex)
{
throw ex;
}
return flag;
}
public async Task<bool> RemoveContactPhone(int empId, string type, DevDBEntities dbContext)
{
bool flag = false;
try
{
var empPhone = dbContext.ContactPhones.FirstOrDefault(m => (m.EmployeeId == empId) && (m.Type == type));
if (empPhone != null)
{
dbContext.ContactPhones.Remove(empPhone);
await dbContext.SaveChangesAsync();
flag = true;
}
}
catch (Exception ex)
{
throw ex;
}
return flag;
}
I'm getting following exception:
Message = "Object reference not set to an instance of an object."
Here with I'm attaching the screenshot for your reference
My requirement is to do all the database UPSERT processes in parallel execution, kindly assist me how to achieve this without any exception using Task
1st)Stop using the context in different threads.
DbContext is NOT thread safe,that alone can cause many strange problems ,even a crazy NullReference exception
Now,are you sure your Parallel code is faster than a non parallel implementation?
I very much doubt that.
From what I see you are don't even changing your Employee object so I don't see why you should load it (twice)
I think all you need is
1)Load the phone which you need to update and set the new Number
2)Delete the unused Mobile
DON'T have to load this record.Just use the default constructor and set the Id.
EF can handle the rest (Of course you need to attach the newly created object)
3)Save your changes
(Do 1,2,3 in 1 method using the same context)
If for some reason you do decide to go with multiple tasks
Create a new context within each Task
Wrap your code in a TransactionScope
Update
I just noticed this:
catch (Exception ex) { throw ex; }
This is bad (you lose the stacktrace)
Either remove the try/catch or use
catch (Exception ex) { throw ; }
Update 2
Some sample code (I assume your input contains the Ids of the entities you want to update/delete)
var toUpdate= ctx.ContactPhones.Find(YourIdToUpdate);
toUpdate.Number = newPhone;
var toDelete= new ContactPhone{ Id = 1 };
ctx.ContactPhones.Attach(toDelete);
ctx.ContactPhones.Remove(toDelete);
ctx.SaveChanges();
If you go with the parallel approach
using(TransactionScope tran = new TransactionScope()) {
//Create and Wait both Tasks(Each task should create it own context)
tran.Complete();
}
Possible places where this error could occur are - employee.ContactPhoneNumbers.FirstOrDefault().Type, employee.ContactPhoneNumbers.FirstOrDefault()
The employee.ContactPhoneNumbers will be possibly null as you are not eager loading it nor you have marked the property as virtual so that it would lazy load.
So to fix this issue:
1. Mark the navigational properties as virtual so that lazy load
public virtual List<ContactPhone> ContactPhoneNumbers { get; set; }
public virtual List<ContactEmail> ContactEmailAddress { get; set; }
Or Eager load the entities using .Include
Or Explicit load the entities
dbContext.Entry(emp).Collection(s => s.ContactPhoneNumbers).Load();
dbContext.Entry(emp).Collection(s => s.ContactEmailAddress ).Load();
I am implementing a city search so I want a Autofill with the functionality where when I select a city I want the ID to be sent back to the API. to populate some other fields.
private async void AutoSuggestBox_TextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args)
{
if (args.Reason == AutoSuggestionBoxTextChangeReason.UserInput)
{
string text = sender.Text;
if (text.Length >= 3)
{
GetCities(text);
sender.ItemsSource = await Task<string[]>.Run(() => { return this.GetSuggestions(text); });
}
else
{
sender.ItemsSource = new string[] { "No suggestions..." };
}
}
}
My Get response class
private async void GetCities(string city)
{
try
{
string baseAddress = Url.url + "searchCities?q="+city+"&access_token=" + tcm;
HttpClient httpClient = new HttpClient();
string co = "";
var content = await httpClient.GetAsync(new Uri(baseAddress));
if (!content.IsSuccessStatusCode)
{
TokenGenerator tc = new TokenGenerator();
tc.GetToken();
tcm = TokenManager.accessT.access_tocken;
HttpClient client = new HttpClient();
content = await client.GetAsync(new Uri(baseAddress));
}
co = await content.Content.ReadAsStringAsync();
AutofillHelper result = JsonConvert.DeserializeObject<AutofillHelper>(co);
foreach (var item in result.data)
{
suggestions = new string [] {item.city} ;
}
}
catch (Exception ex)
{
dispatcherTimer.Stop();
throw new Exception(ex.ToString());
}
}
private string[] GetSuggestions(string text)
{
string[] result = null;
result = suggestions.Where(x => x.StartsWith(text)).ToArray();
return result;
}
My Get Set
class Autofill
{
public string city_id { get; set; }
public string city { get; set; }
}
class AutofillHelper
{
public List<Autofill> data { get; set; }
}
I want it to display the response from the API for the person to select it. A Run time error is thrown. Can someone please guide me what has gone wrong.
Any kind of help is appreciated...
I'm writing a simple messaging module so one process can publish messages and another can subscribe to them. I'm using EF/SqlServer as the out of process communication mechanism. A "Server" is just a name that a publisher/subscriber pair have in common (could have been called a "Channel").
I have the following method which adds a row to the database representing a named "Server"
public void AddServer(string name)
{
if (!context.Servers.Any(c => c.Name == name))
{
context.Servers.Add(new Server { Name = name });
}
}
The problem I'm having is that when I start two clients at the same time, only one is supposed to add a new Server entry, however, that is not how it's working out. I'm actually getting the very wrong result of two entries with the same name, and realizing that an Any() guard is not sufficient for this.
The Entity for Server uses an int PK and supposedly my repository would enforce the uniqueness of the Name field. I'm starting to think this isn't going to work though.
public class Server
{
public int Id { get; set; }
public string Name { get; set; }
}
The two ways I think I could fix this both seem less than ideal:
String primary keys
Ignoring Exception
This is the issue of concurrency, right?
How can I deal with it in this situation where I want two clients to call the repository with the same Name but get a result of only one row with that name in the database?
Update: Here is the Repository Code
namespace MyBus.Data
{
public class Repository : IDisposable
{
private readonly Context context;
private readonly bool autoSave;
public delegate Chain Chain(Action<Repository> action);
public static Chain Command(Action<Repository> action)
{
using (var repo = new Data.Repository(true))
{
action(repo);
}
return new Chain(next => Command(next));
}
public Repository(bool autoSave)
{
this.autoSave = autoSave;
context = new Context();
}
public void Dispose()
{
if (autoSave)
context.SaveChanges();
context.Dispose();
}
public void AddServer(string name)
{
if (!context.Servers.Any(c => c.Name == name))
{
context.Servers.Add(new Server { Name = name });
}
}
public void AddClient(string name, bool isPublisher)
{
if (!context.Clients.Any(c => c.Name == name))
{
context.Clients.Add(new Client
{
Name = name,
ClientType = isPublisher ? ClientType.Publisher : ClientType.Subscriber
});
}
}
public void AddMessageType<T>()
{
var typeName = typeof(T).FullName;
if (!context.MessageTypes.Any(c => c.Name == typeName))
{
context.MessageTypes.Add(new MessageType { Name = typeName });
}
}
public void AddRegistration<T>(string serverName, string clientName)
{
var server = context.Servers.Single(c => c.Name == serverName);
var client = context.Clients.Single(c => c.Name == clientName);
var messageType = context.MessageTypes.Single(c => c.Name == typeof(T).FullName);
if (!context.Registrations.Any(c =>
c.ServerId == server.Id &&
c.ClientId == client.Id &&
c.MessageTypeId == messageType.Id))
{
context.Registrations.Add(new Registration
{
Client = client,
Server = server,
MessageType = messageType
});
}
}
public void AddMessage<T>(T item, out int messageId)
{
var messageType = context.MessageTypes.Single(c => c.Name == typeof(T).FullName);
var serializer = new XmlSerializer(typeof(T));
var sb = new StringBuilder();
using (var sw = new StringWriter(sb))
{
serializer.Serialize(sw, item);
}
var message = new Message
{
MessageType = messageType,
Created = DateTime.UtcNow,
Data = sb.ToString()
};
context.Messages.Add(message);
context.SaveChanges();
messageId = message.Id;
}
public void CreateDeliveries<T>(int messageId, string serverName, string sendingClientName, T item)
{
var messageType = typeof(T).FullName;
var query = from reg in context.Registrations
where reg.Server.Name == serverName &&
reg.Client.ClientType == ClientType.Subscriber &&
reg.MessageType.Name == messageType
select new
{
reg.ClientId
};
var senderClientId = context.Clients.Single(c => c.Name == sendingClientName).Id;
foreach (var reg in query)
{
context.Deliveries.Add(new Delivery
{
SenderClientId = senderClientId,
ReceiverClientId = reg.ClientId,
MessageId = messageId,
Updated = DateTime.UtcNow,
DeliveryStatus = DeliveryStatus.Sent
});
}
}
public List<T> GetDeliveries<T>(string serverName, string clientName, out List<int> messageIds)
{
messageIds = new List<int>();
var messages = new List<T>();
var clientId = context.Clients.Single(c => c.Name == clientName).Id;
var query = from del in context.Deliveries
where del.ReceiverClientId == clientId &&
del.DeliveryStatus == DeliveryStatus.Sent
select new
{
del.Id,
del.Message.Data
};
foreach (var item in query)
{
var serializer = new XmlSerializer(typeof(T));
using (var sr = new StringReader(item.Data))
{
var t = (T)serializer.Deserialize(sr);
messages.Add(t);
messageIds.Add(item.Id);
}
}
return messages;
}
public void ConfirmDelivery(int deliveryId)
{
using (var context = new Context())
{
context.Deliveries.First(c => c.Id == deliveryId).DeliveryStatus = DeliveryStatus.Received;
context.SaveChanges();
}
}
}
}
You could keep the int primary key, but also define a unique index on the Name column.
This way, in concurrency situations only the first insert would be successful; any subsequent clients that attempt to insert the same server name would fail with an SqlException.
I'm currently using this solution:
public void AddServer(string name)
{
if (!context.Servers.Any(c => c.Name == name))
{
context.Database.ExecuteSqlCommand(#"MERGE Servers WITH (HOLDLOCK) AS T
USING (SELECT {0} AS Name) AS S
ON T.Name = S.Name
WHEN NOT MATCHED THEN
INSERT (Name) VALUES ({0});", name);
}
}
As an exercise in thoroughness I (think I) solved this problem another way, which preserves the type safety of the EF context but adds a bit of complexity:
First, this post, I learned how to add a unique constraint to the Server table:
Here's the Context code:
public class Context : DbContext
{
public DbSet<MessageType> MessageTypes { get; set; }
public DbSet<Message> Messages { get; set; }
public DbSet<Delivery> Deliveries { get; set; }
public DbSet<Client> Clients { get; set; }
public DbSet<Server> Servers { get; set; }
public DbSet<Registration> Registrations { get; set; }
public class Initializer : IDatabaseInitializer<Context>
{
public void InitializeDatabase(Context context)
{
if (context.Database.Exists() && !context.Database.CompatibleWithModel(false))
context.Database.Delete();
if (!context.Database.Exists())
{
context.Database.Create();
context.Database.ExecuteSqlCommand(
#"alter table Servers
add constraint UniqueServerName unique (Name)");
}
}
}
}
Now I need a way to selectively ignore exception when saving. I did this by adding the following members to my repository:
readonly List<Func<Exception, bool>> ExceptionsIgnoredOnSave =
new List<Func<Exception, bool>>();
static readonly Func<Exception, bool> UniqueConstraintViolation =
e => e.AnyMessageContains("Violation of UNIQUE KEY constraint");
Along with a new extension method to loop keep from depending on the position of the text in the inner exception chain:
public static class Ext
{
public static bool AnyMessageContains(this Exception ex, string text)
{
while (ex != null)
{
if(ex.Message.Contains(text))
return true;
ex = ex.InnerException;
}
return false;
}
}
And I modified the Dispose method of my Repository to check if the exception should be ignored or re-thrown:
public void Dispose()
{
if (autoSave)
{
try
{
context.SaveChanges();
}
catch (Exception ex)
{
if(!ExceptionsIgnoredOnSave.Any(pass => pass(ex)))
throw;
Console.WriteLine("ignoring exception..."); // temp
}
}
context.Dispose();
}
Finally, in the method which invokes the Add, I add the acceptable condition to the list:
public void AddServer(string name)
{
ExceptionsIgnoredOnSave.Add(UniqueConstraintViolation);
if (!context.Servers.Any(c => c.Name == name))
{
var server = context.Servers.Add(new Server { Name = name });
}
}