I am trying to fetch the data from another microservice. Suppose you have three microservices: State, School and Student. You get the Flux< School> via stateId from SchoolRepository and for each School you are calling Student microservice via webclient which returns Flux< Students> and setting it to Flux< School>. Shortly, what I am trying to do is:
public Flux<School> getBySchool(Long stateId){
Flux<School> schoolList=schoolRepository.findByStateId(stateId);
//And for each school I want to do this
Flux<Student> studentsfound=webClient.get().uri("bla bla bla"+school.getSchoolId).exchange().flatMapMany(response->response.bodyToFlux(Student.class));
//I have a List<Student> entity in School domain, so I want Flux<Student> --> List<Student> and add it to School. Something like school.setStudentList(studentListReturned).
//And then return Flux<Stundent>
}
How can I iterate through Flux< School > , and after getting Flux< Student > how can I add it to the appropriate Flux< School> ? Thank you in advance.
UPDATE
SOLUTION
Many thanks to #K.Nicholas. I was able to solve the problem as below, but more elegant solutions are welcome. And I'm subscribing to schoolList in my controller as I have to return Flux< School> from service layer to controller layer.
public Flux<School> getBySchoolWithStudents(Long stateId) {
Flux<School> schoolList = schoolRepository.findByStateId(stateId);
return schoolList.flatMap(school -> {
Flux<Student> studentFlux = webClientBuilder.build().get().uri(REQUEST_URI + school.getSchoolId()).exchange().flatMapMany(response -> response.bodyToFlux(Student.class));
return studentFlux.collectList().map(list -> {
school.setStudentList(list);
return school;
});
});
}
EDIT: Second attempt. So, nothing particularly special that I can see. Uses the collectList method and assigns it in a map function. The map function returns the school object that is in scope. I had to do a bit of debugging to ensure my classes supported serialization/deserialization properly.
WebClient.create().get().uri(URI.create("http://localhost:8082/ss/school?state=CA"))
.accept(MediaType.APPLICATION_JSON)
.exchange()
.flatMapMany(cr->cr.bodyToFlux(School.class))
.flatMap(school->{
return WebClient.create().get().uri(URI.create("http://localhost:8081/ss/student?school="+school.getName()))
.accept(MediaType.APPLICATION_JSON)
.exchange()
.flatMapMany(crs->crs.bodyToFlux(Student.class))
.collectList()
.map(sl->{
school.setStudents(sl);
return school;
});
})
.subscribe(System.out::println);
Related
DISCLAIMER: Since we are all familiar with it, i will be using contoso university design to explain my question. Also, i am using EF core and .net core 2.0 on a mvc code first design.
I am developing a very generic RESTful API that works on any model. It has one method for each of create, read, update and delete operation in only one controller, the route of this is
[Route("/api/{resource}")]
Resource is the entity that the client wants to work with, for example if someone wants to get all Courses using the api he has to do a GET request on http://www.example.com/api/course/ or http://www.example.com/api/course/2 to get one by id and the following code will do the job.
[HttpGet("{id:int:min(1)?}")]
public IActionResult Read([FromRoute] string resource, [FromRoute] int? id)
{
//find resourse in models
IEntityType entityType = _context.Model
.GetEntityTypes()
.FirstOrDefault(x => x.Name.EndsWith($".{resource}", StringComparison.OrdinalIgnoreCase));
if (entityType == null) return NotFound(resource);
Type type = entityType.ClrType;
if (id == null)//select all from table
{
var entityRows = context.GetType().GetMethod("Set").MakeGenericMethod(type).Invoke(context, null);
if (entityRows == null)
return NoContent();
//TODO: load references (1)
return Ok(entityRows);
}
else //select by id
{
var entityRow = _context.Find(type, id);
if (entityRow == null)
return NoContent();
//TODO: load references (2)
return Ok(entityRows);
}
}
This small piece of code will do the magic with one small exception, intermediate collections will not be loaded. Given our example, the fetched course or courses will have no info for CourseInstructor (the intermediate collection in between Course and Person). I am trying to find a way to Eager load the navigation properties only if it is a collection; or by any other condition that will ensure that only many-to-many relationships are loaded.
For //TODO: load reference (2) i could use
_context.Entry(entityRow).Collection("CourseInsructor").Load();
On runtime if i could find all the navigation properties (filtered by spoken condition) and foreach of them i did Load(), i should get the desired result. My problem is when i get all (when id is null) the entityRows is type 'InternalDbSet' which is an unknown model.
So for the two TODOs i need some help on doing the following steps
1: find navigation properties of many-to-many relationships only
2: load them
Any suggestions?
In general, this seems like a very bad idea to me. While the CRUD stuff is going to be identical for most resources, there will be variances (as you've now run into). There's also something to be said for having a self-documenting API: with individual controllers, you know which resources can be accessed by nature of having a controller associated with that resource. With they way you're doing it, it's a complete black box. This also will of course effect any sort of actual generated API documentation. For example, if you were to include Swagger in your project, it would not be able to determine what you're doing here. Finally, you're now having to use reflection for everything, which will effect your performance.
What I would suggest instead is creating a base abstract controller and then creating a controller for each unique resource that inherits from that, for example:
public abstract class BaseController<TEntity> : Controller
where TEntity : class, new()
{
protected readonly MyContext _context;
public BaseController(MyContext context)
{
_context = context ?? throw new ArgumentNullException(nameof(context));
}
...
[HttpGet("create")]
public IActionResult Create()
{
var model = new TEntity();
return View(model);
}
[HttpPost("create")]
public async Task<IActionResult> Create(TEntity model)
{
if (ModelState.IsValid)
{
_context.Add(model);
await _context.SaveChangesAsync();
return RedirectToAction("Index");
}
return View(model);
}
...
}
I just wanted to give a quick example, but you'd build out all the rest of the CRUD methods in the same fashion, generically using TEntity. Then, for each actual resource, you simply do:
public class WidgetController : BaseController<Widget>
{
public WidgetController(MyContext context)
: base(context)
{
}
}
No duplication of code, but you've now got an actual real controller backing the resource, aiding both the innate and possibly explicit documentation of your API. And, no reflection anywhere.
Then, to solve problems like what you have here, you can add hooks to your base controller: essentially just virtual methods that are utilized in your base controller's CRUD actions and do nothing or just default things. However, you can then override these in your derived controllers to stub in additional functionality. For example, you can add something like:
public virtual IQueryable<TEntity> GetQueryable()
=> _context.Set<TEntity>();
Then, in your derived controller, you can do something like:
public class CourseController : BaseController<Course>
{
...
public override IQueryable<Course> GetQueryable()
=> base.GetQueryable().Include(x => x.CourseInstructors).ThenInclude(x => x.Instructor);
So, for example, you'd make your BaseController.Index action, perhaps, utilize GetQueryable() to get the list of entities to display. Simply by overriding this on the derived class, you can alter what happens based on the context of a particular type of resource.
I have a Vehicle with an association to Model, Model has an association to Make.
Here is my Generic Repository as pertaining to associations as LazyLoadingEnabled = false in my project:
public IQueryable<T> IncludeMultiple<T1>(params Expression<Func<T, object>>[] associations) where T1 : class
{
var source = (IQueryable<T>)DbContext.Set<T>();
if (associations != null)
{
foreach (Expression<Func<T, object>> path in associations)
source = DbExtensions.Include<T, object>(source, path);
}
return source;
}
In my api controller, I am using Unit of work pattern. Here is my GetAll method:
public IEnumerable<Vehicle> GetAll()
{
var vehicles = Uow.VehicleRepository.IncludeMultiple<Vehicle>(c => c.VehicleModel).ToList();
return vehicles;
}
Everything works fine and Json retrieves the Vehicle class data as well as the related VehicleModel class data.
However, Vehicle has no direct association to VehicleMake, only VehicleModel does. Now, if my GetAll method has this:
public IEnumerable<Vehicle> GetAll()
{
var vehicles = Uow.VehicleRepository.IncludeMultiple<Vehicle>(c => c.VehicleModel, c => c.VehicleModel.VehicleMake).ToList();
return vehicles;
}
while I see in debug that vehicles does indeed have the vehicles and their relevant VehicleModel and VehicleMake data, it returns a Http 500 error in Fiddler.
Update:
Added another association in Vehicle called "Test", with the GetAll method being:
(c => c.VehicleModel, c => c.Test)
No error, all data was returned via fiddler. So, it appears that a "Non-direct association" (ie Vehicle -> VehicleMake) is the cause of the error.
Question:
What would be the correct way to retrieving the relevant Vehicle data and its associated classes' data and return it to Json while not getting a Http 500 error?
*SOLVED *
This works:
public HttpResponseMessage GetAll()
{
var vehicles = from data in Uow.VehicleRepository.IncludeMultiple<Vehicle>(c => c.VehicleModel,c => c.VehicleModel.VehicleMake)
select new
{
VehDesc = data.Description,
VehVIN = data.VIN,
VehTransmissionType = data.TransmissionType,
VehFuelType = data.FuelType,
VehYear = data.Year,
VehMileage = data.Mileage,
VehCylinderSize = data.CylinderSize,
VehEngineSize = data.EngineSize,
VehVehicleModel = data.VehicleModel.Name,
VehMakeName = data.VehicleModel.VehicleMake.Name
};
return Request.CreateResponse(HttpStatusCode.OK, vehicles);
}
Basically,
1. I used an HttpResponseMessage as my return type;
2. I used projection to create an anonymous type;
Why did I have to do this?
As near as I can tell, the issue centered on JSON receiving a "circular" return with VehicleModel and VehicleMake. That is, VehicleModel had a association to VehicleMake and VehicleMake has a collection of VehicleModels. When I looked in my debug code I could see a cascade of VehicleModel to VehicleMake to VehicleModel, etc, etc, etc, so to me that meant it was circular.
If anyone knows a better way w/o using anonymous type nor removing the virtual keyword from my navigation properties, I would certainly like to know it. But this does truly work.
FinalNote: Be sure NOT to use the model's property names in anonymous type, ie replace property "TransmissionType" with something like "VehTransmissionType".
In my API, I'd like to have routes like GET /api/v1/widgets/1,2,3 and GET /api/v1/widgets/best-widget,major-widget,bob-the-widget
public class WidgetsController : MyApiController
{
public ActionResult Show(IEnumerable<int> ids)
{
}
public ActionResult Show(IEnumerable<string> names)
{
}
}
I've got routes set up to get me to the action, but I can't figure out how to turn 1,2,3 into new List<int>(){1, 2, 3} and so on. Of course, I could just take a string and parse it in my action, but I'd like to avoid going that route.
One thing that came to mind was to put something in the OnActionExecuting method, but then I wasn't sure exactly what to put in there (I could hack something together, obviously, but I'm trying to write something reusable.)
The main questions I have are how to know whether I need to do anything at all (sometimes the ValueProviders upstream will have figured everything out), and how to handle figuring out the type to cast to (e.g., how do I know that in this case I need to go to a collection of ints, or a collection of strings, and then how do I do that?)
By the way, I had the idea of implementing a ValueProvider as well, but got lost on similar questions.
I can't figure out how to turn 1,2,3 into new List(){1, 2, 3} and so on.
To avoid polluting each controller action that needs to receive this parameter I would recommend a custom model binder:
public class IdsModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var result = base.BindModel(controllerContext, bindingContext);
var ids = bindingContext.ValueProvider.GetValue("ids");
if (ids != null)
{
return ids.AttemptedValue
.Split(',')
.Select(id => int.Parse(id))
.ToList();
}
return result;
}
}
and then register the model binder in Application_Start:
ModelBinders.Binders.Add(typeof(IEnumerable<int>), new IdsModelBinder());
and finally your controller action might look like this (and from what I can see in your question it already does look like this :-)):
public ActionResult Show(IEnumerable<int> ids)
{
...
}
and the custom model binder will take care for parsing the ids route token to the corresponding IEnumerable<int> value.
You could do the same with the IEnumerable<string> where you would simply remove the .Select clause in the corresponding model binder.
if your URL was
/api/v1/widgets/Show?names=best-widget&names=major-widget&names=bob-the-widget
This would bind neatly by itself :)
No need to override modelbinders in this case.
The querystring-variable names will bind to your Show-method_
public ActionResult Show(IEnumerable<string> names)
Hope this helps!
I'm relatively new to ASP.Net MVC and so I'm not sure if there is an easier way of doing this or not, however my approach would be to do something like the following:
public class WidgetsController : MyApiController
{
public ActionResult Show(string ids)
{
List<int> parsedIds = new List<int>();
foreach (var id in ids.Split(','))
{
parsedIds.Add(int.Parse(id));
}
return Show(parsedIds);
}
private ActionResult Show(List<int> ids);
}
You might also want to add some more sophisticated error handling for cases where the IDs entered can't be parsed, but thats the general approach I would use.
Making my first steps in RIA Services (VS2010Beta2) and i encountered this problem:
created an EF Model (no POCOs), generic repository on top of it and a RIA Service(hosted in an ASP.NET MVC application) and tried to get data from within the ASP.NET MVC application: worked well.
Next step: Silverlight client. Got a reference to the RIAService (through its context), queried for all the records of the repository and got them into the SL application as well (using this code sample):
private ObservableCollection<Culture> _cultures = new ObservableCollection<Culture>();
public ObservableCollection<Culture> cultures
{
get { return _cultures; }
set
{
_cultures = value;
RaisePropertyChanged("cultures");
}
}
....
//Get cultures
EntityQuery<Culture> queryCultures = from cu in dsCtxt.GetAllCulturesQuery()
select cu;
loCultures = dsCtxt.Load(queryCultures);
loCultures.Completed += new EventHandler(lo_Completed);
....
void loAnyCulture_Completed(object sender, EventArgs e)
{
ObservableCollection<Culture> temp=
new ObservableCollection<Culture>loAnyCulture.Entities);
AnyCulture = temp[0];
}
The problem is this: whenever i try to edit some data of a record (in this example the first record) i get this error:
This EntitySet of type 'Culture' does not support the 'Edit' operation.
I thought that i did something weird and tried to create an object of type Culture and assign a value to it: it worked well!
What am i missing? Do i have to declare an EntitySet? Do i have to mark it? Do i have to...what?
Thanks in advance
It turns out that in the DomainService class one has to implement (or at least to mark "placeholder methods") as "Edit", "Delete",... eg
[Delete]
public void DeleteCulture(Culture currentCulture)
{
throw new NotImplementedException("UpdateCulture not Implemented yet");
}
[Insert]
public void InsertCulture(Culture newCulture)
{
throw new NotImplementedException("InsertCulture not Implemented yet");
}
This way the OrganizationDomainContextEntityContainer class creates an EntitySet with parameter EntitySetOperations.All (meaning that all the CUD operations are available).
Hope it's useful for someone in the future!
If I run the following code it throws the following error:
An entity object cannot be referenced by multiple instances of IEntityChangeTracker
public void Save(Category category)
{
using(var db = new NorthwindContext())
{
if(category.CategoryID == 0)
{
db.AddToCategorySet(category);
}
else
{
//category.RemoveTracker();
db.Attach(category);
}
db.SaveChanges();
}
}
The reason is of course that the category is sent from interface which we got from GetById method which already attached the EntityChangeTracker to the category object. I also tried to set the entity tracker to null but it did not update the category object.
protected void Btn_Update_Category_Click(object sender, EventArgs e)
{
_categoryRepository = new CategoryRepository();
int categoryId = Int32.Parse(txtCategoryId.Text);
var category = _categoryRepository.GetById(categoryId);
category.CategoryName = txtUpdateCategoryName.Text;
_categoryRepository.Save(category);
}
I'm still learning Entity Framework myself, but maybe I can help a little. When working with the Entity Framework, you need to be aware of how you're handling different contexts. It looks like you're trying to localize your context as much as possible by saying:
public void Save(Category category)
{
using (var db = new NorthwindContext())
{
...
}
}
... within your data access method. Did you do the same thing in your GetById method? If so, did you remember to detach the object you got back so that it could be attached later in a different context?
public Category GetById(int categoryId)
{
using (var db = new NorthwindContext())
{
Category category = (from c in db.Category where Category.ID == categoryId select c).First();
db.Detach(category);
}
}
That way when you call Attach it isn't trying to step on an already-attached context. Does that help?
As you pointed out in your comment, this poses a problem when you're trying to modify an item and then tell your database layer to save it, because once an item is detached from its context, it no longer keeps track of the changes that were made to it. There are a few ways I can think of to get around this problem, none of them perfect.
If your architecture supports it, you could expand the scope of your context enough that your Save method could use the same context that your GetById method uses. This helps to avoid the whole attach/detach problem entirely, but it might push your data layer a little closer to your business logic than you would like.
You can load a new instance of the item out of the new context based on its ID, set all of its properties based on the category that is passed in, and then save it. This costs two database round-trips for what should really only need one, and it isn't very maintainable.
You can dig into the context itself to mark the Category's properties as changed.
For example:
public void Save(Category category)
{
using (var db = new NorthwindContext())
{
db.Attach(category);
var stateEntry = db.ObjectStateManager.GetObjectStateEntry(category);
foreach (var propertyName in stateEntry.CurrentValues.DataRecordInfo.FieldMetadata.Select(fm => fm.FieldType.Name)) {
stateEntry.SetModifiedProperty(propertyName);
}
db.SaveChanges();
}
}
This looks a little uglier, but should be more performant and maintainable overall. Plus, if you want, you could make it generic enough to throw into an extension method somewhere so you don't have to see or repeat the ugly code, but you still get the functionality out of it.