Here is my existing code, which fetches a user and gets his groups.
User user = new FetchUserByUsernameServiceCommand(Username, Context).Execute();
var groupList = user.JoinedGroups.ToList<Group>();
return groupList;
Each Group has a navigation property Image which has an ImageUrl.
I would like to Include the Image of each Group in the groupList, but Include is not available on the Lis<Group> groupList because it is not attached to a Context.
How can I include the Image navigation property for each Group?
You can either extend your command class and constructor to accept expressions for the navigation properties to include:
private string _userName;
private MyContext _context;
private Expression<Func<User, object>>[] _includes;
public FetchUserByUsernameServiceCommand(string userName,
MyContext context, params Expression<Func<User, object>>[] includes)
{
_userName = userName;
_context = context;
_includes = includes;
}
public User Execute()
{
IQueryable<User> query = _context.Users;
if (_includes != null)
{
foreach (var include in _includes)
query = query.Include(include);
}
return query.SingleOrDefault(u => u.UserName);
}
You would call this like so:
User user = new FetchUserByUsernameServiceCommand(
Username, Context, u => u.JoinedGroups.Select(g => g.Image))
.Execute();
var groupList = user.JoinedGroups.ToList();
return groupList;
Or you can use explicit loading:
User user = new FetchUserByUsernameServiceCommand(
Username, Context)
.Execute();
var groupList = Context.Entry(user).Collection(u => u.JoinedGroups).Query()
.Include(g => g.Image)
.ToList();
return groupList;
Note that only the first option is eager loading in the sense that user plus groups plus images are loaded together in a single database request. The second option (explicit loading) will run two database queries - the first one that only loads the User object in the command executor and the second one that loads groups plus images for that user.
Related
I'm trying to complete a general repository for all of the entities in my application. I Have a BaseEntity with property Id, CreatorId and LastModifiedUserId. Now I'd like to Update a record in a collection, without having to modify the field CreatorId, so I have (from the client) an Entity valorized with some fields updated that I want to update.
Hi have 2 ways:
UpdateOneAsync
ReplaceOneAsync
The repo is created like this:
public class BaseRepository<T> : IRepository<T> where T : BaseEntity
{
public async Task<T> Replace/Update(T entity){...}
}
So it's very hard to use Update(1), since I should retrieve with reflection all the fields of T and exclude the ones that I don't want to update.
With Replace(2) I cannot find a way to specify which fields i should exclude when replacing an object with another. Projectionproperty in FindOneAndReplaceOptions<T>() just excludes the fields on the document that is returned after the update.
Am I missing a way in the replace method to exclude the fields or should I try to retrieve the fields with reflection and use a Update?
I don't know if this solution is ok for you .. what i do is :
Declare in Base Repo a method like
public virtual bool Update(TEntity entity, string key)
{
var result = _collection.ReplaceOne(x => x.Id.Equals(key), entity, new UpdateOptions
{
IsUpsert = false
});
return result.IsAcknowledged;
}
then in your controller when you want to update your entities is there where you set the prop you want to change .. like:
[HttpPut]
[ProducesResponseType(typeof(OrderDTO), 200)]
[ProducesResponseType(400)]
public async Task<ActionResult<bool>> Put([FromBody] OrderDTO value)
{
try
{
if (!ModelState.IsValid) return BadRequest(ModelState);
var orderOnDb = await _orderService.FindAsync(xx => xx.Id == value.Id);
if (orderOnDb == null) return BadRequest(Constants.Error.NOT_FOUND_ON_MONGO);
// SET PROPERTY TO UPDATE (MANUALLY)
orderOnDb.LastUpdateDate = DateTime.Now;
orderOnDb.PaymentMethod = value.PaymentMethod;
orderOnDb.StateHistory = value.StateHistory;
//Save on db
var res = await _orderRepo.UpdateAsync(orderOnDb, orderOnDb.Id);
return res;
}
catch (Exception ex)
{
_logger.LogCritical(ex, ex.Message);
throw ex;
}
}
Hope it helps you!!!
I want to store globally the object User (that is the table USER in my db) in my HomeController, in that way i don't have to instantiate it in every single action.
I found the following solution that works pretty fine
public class HomeController : Controller
{
private DatabaseContext db = new DatabaseContext();
private User currentUser;
private User CurrentUser
{
get
{
if (User.Identity.IsAuthenticated)
//This function returns the object "User" (table USER in db) based on the PK of the table
currentUser = CustomDbFunctions.GetUserEntityFromUsername(User.Identity.Name, db);
return currentUser;
}
}
public ActionResult Index()
{
if (User.Identity.IsAuthenticated)
return View(CurrentUser);
else
return Redirect("login");
}
}
I'd like to know if there's a better (or more elegant) way to achieve the same goal.
Please note that i'm not using the MembershipProvider.
In your example the user object is instantiated in every single action (in contrast to what you said). This is because actions are usually invoked per http request and controller instances are disposed after each use.
Your code shares the instance structurally (you don't have to repeat the code) which is ok but what about sharing the code between different controllers? I'd suggest to refactor your GetUserEntityFromUsername a little bit so that you retrieve the object only once per request, using the Items container to get the request scope:
public class CustomDbFunctions
{
const string itemsUserKey = "_itemsUserKey";
public static User GetUserEntityFromUsername( IPrincipal principal, DatabaseContext db )
{
if ( principal == null || principal.Identity == null ||
!principal.Identity.IsAuthenticated
)
return null;
if ( HttpContext.Current.Items[itemsUserKey] == null )
{
// retrieve the data from the Db
var user = db.Users.FirstOrDefault( u => u.Name == User.Identity.Name );
HttpContext.Current.Items[itemsUserKey] = user;
}
return (User)HttpContext.Current.Items[itemsUserKey];
}
This way your wrapper takes care of retrieving the instance from the database once per request.
Note that this requires sharing your database context as entities should not be reused on different contexts. Fortunately, this can be done in a similar way:
public class CustomDbFunctions
{
const string dbUserKey = "dbUserKey";
public static DatabaseContext CurrentDatabaseContext
{
get
{
if ( HttpContext.Current.Items[dbUserKey] == null )
{
DatabaseContext ctx = new DatabaseContext(); // or any other way to create instance
HttpContext.Current.Items[dbUserKey] = ctx;
}
return (DatabaseContext)HttpContext.Current.Items[dbUserKey];
}
}
This way, the context instance, shared per request, is always available as
CustomDbFunctions.CurrentDatabaseContext
First of all, please take a look at how IModel is used in this example:
#SuppressWarnings("serial")
public static List<IColumn> getTableColumns(
final ReportParams reportParams, final boolean columnsSortable
) {
List<IColumn> columns = new ArrayList<IColumn>();
final Map<String,ToolInfo> eventIdToolMap = Locator.getFacade().getEventRegistryService().getEventIdToolMap();
// site
if(Locator.getFacade().getReportManager().isReportColumnAvailable(reportParams, StatsManager.T_SITE)) {
columns.add(new PropertyColumn(new ResourceModel("th_site"), columnsSortable ? ReportsDataProvider.COL_SITE : null, ReportsDataProvider.COL_SITE) {
#Override
public void populateItem(Item item, String componentId, IModel model) {
final String site = ((Stat) model.getObject()).getSiteId();
String lbl = "", href = "";
Site s = null;
try{
s = Locator.getFacade().getSiteService().getSite(site);
lbl = s.getTitle();
href = s.getUrl();
}catch(IdUnusedException e){
lbl = (String) new ResourceModel("site_unknown").getObject();
href = null;
}
item.add(new ImageWithLink(componentId, null, href, lbl, "_parent"));
}
});
}
And my questions are:
How does populateItem get an input for IModel parameter?
I cannot find any code in this application, which explicitly constructs IModel object. Is it correct for me to assume that the object is retrieved directly from a table in the database? I'm thinking of this because Mapping Hibernate is used for this application.
The models are created using the IDataProvider you provide to the DataTable (DataTable constructor will also take your IColumn List) .
The IDataProvider could use Hibernate - hard to say without having more information on that implementation.
I have a Team table and a Player table in many to many relationship. There is a linking table called TeamOnPlayer. EF with POCO generates navigation propertie called Person for the Team entity and also generates a nav. prop. called Team for the People entity.
I'm trying to insert a new record into the TeamOnPlayer table, but EF and POCO hides it. I tried to do this:
public static void AddPersonToTeam(int TeamId, int PersonId)
{
using (var ef = new korfballReportEntities())
{
var team = GetTeam(TeamId);
var person = GetPerson(PersonId);
team.Person.Add(person);
person.Team.Add(team);
ef.SaveChanges();
}
}
The GetTeam(TeamId) and GetPerson(PersonId) gets the right team and person:
public static Team GetTeam(int id)
{
using (var ef = new korfballReportEntities())
{
var q = from l in ef.Team
where l.Id == id
select l;
return q.Single();
}
}
public static Person GetPerson(int id)
{
using (var ef = new korfballReportEntities())
{
var query = from p in ef.Person
where p.Id == id
select p;
return query.Single();
}
}
When it tries to call the team.Person.Add(person) it throws an exception:
"The ObjectContext instance has been disposed and can no longer be used for operations that require a connection." System.Exception {System.ObjectDisposedException}
Can anyone please show me the correct way?
Edit
Now I understand what the problem was, thanks to you. I was a bit confused about the using blocks you included. For example this:
using (var ef = new korfballReportEntities())
{
//switch lazy loading off, only in this single context
ef.Configuration.LazyLoadingEnabled = false;
var repository = new MyRepository(ef);
repository.AddPersonToTeam(int TeamId, int PersonId);
}
Where should I put it?
I've done something else. I simply did this, and it worked fine.
public static void AddPersonToTeam(int TeamId, int PersonId)
{
using (var ef = new korfballReportEntities())
{
var q = from t in ef.Team
where t.Id == TeamId
select t;
var team = q.Single();
var q2 = from p in ef.Person
where p.Id == PersonId
select p;
var person = q2.Single();
try
{
team.Person.Add(person);
person.Team.Add(team);
}
catch (Exception e)
{
}
ef.SaveChanges();
}
}
The only problem is, that i coludn't reuse my GetPerson(int id) and GetTeam(int id) method.
What do you think? Is it okay? Is this an ugly way?
My guess is that you are working with lazy loading - your navigation properties Team.Person and Person.Team are marked as virtual in your entity classes. The result is that your methods GetTeam and GetPerson do not exactly return Team and Person objects but instances of dynamically created proxy classes which are derived from those entities. This dynamic proxy supports lazy loading which means that EF tries to load the navigation collections Team.Person and Person.Team when you access them for the first time. This happens in your AddPersonToTeam method when you call Add on these collections.
Now the problem is that the proxies are created within an context which you immediately dispose in your GetTeam and GetPerson methods (at the end of the using block). The proxies have stored a reference to this context internally and will use this context to load the navigation collections from the database.
Because these contexts are already disposed you get the exception.
You should redesign your code a bit: Don't create a new context in your repository methods GetTeam and GetPerson. You should instead use the same context for all operations: Retrieving the Team, retrieving the Person and adding the relationship. For example:
public static void AddPersonToTeam(int TeamId, int PersonId)
{
using (var ef = new korfballReportEntities())
{
var team = GetTeam(ef, TeamId);
var person = GetPerson(ef, PersonId);
team.Person.Add(person);
//person.Team.Add(team); <- not necessary, EF will handle this
ef.SaveChanges();
}
}
public static Team GetTeam(korfballReportEntities ef, int id)
{
var q = from l in ef.Team
where l.Id == id
select l;
return q.Single();
}
public static Person GetPerson(korfballReportEntities ef, int id)
{
var query = from p in ef.Person
where p.Id == id
select p;
return query.Single();
}
Another approach is to make your "Repository"/"Service" not static, inject the context into the constructor and then use this context throughout the repository. Then you don't need to pass in the context into every method. A rough sketch:
using (var ef = new korfballReportEntities())
{
var repository = new MyRepository(ef);
repository.AddPersonToTeam(int TeamId, int PersonId);
}
public class MyRepository
{
private readonly korfballReportEntities _ef;
public MyRepository(korfballReportEntities ef)
{
_ef = ef;
}
public void AddPersonToTeam(int TeamId, int PersonId)
{
var team = GetTeam(TeamId);
var person = GetPerson(PersonId);
team.Person.Add(person);
_ef.SaveChanges();
}
public Team GetTeam(int id)
{
var q = from l in _ef.Team
where l.Id == id
select l;
return q.Single();
}
public Person GetPerson(int id)
{
var query = from p in _ef.Person
where p.Id == id
select p;
return query.Single();
}
}
Edit
One little thing about performance tuning: In this specific case lazy loading is not necessary and more disturbing. It causes to load a (potentially long) collection team.Person when you want to add only one additional Person to the collection. You can switch off lazy loading for this particular operation (I refer to my second example):
using (var ef = new korfballReportEntities())
{
//switch lazy loading off, only in this single context
ef.Configuration.LazyLoadingEnabled = false;
var repository = new MyRepository(ef);
repository.AddPersonToTeam(int TeamId, int PersonId);
}
public void AddPersonToTeam(int TeamId, int PersonId)
{
var team = GetTeam(TeamId);
var person = GetPerson(PersonId);
// if lazy loading is off, the collecton is null, so we must instantiate one
if (team.Person == null)
team.Person = new List<Person>();
team.Person.Add(person);
_ef.SaveChanges();
}
In Asp.net MVC the url structure goes like
http://example.com/{controller}/{action}/{id}
For each "controller", say http://example.com/blog, there is a BlogController.
But my {controller} portion of the url is not decided pre-hand, but it is dynamically determined at run time, how do I create a "dynamic controller" that maps anything to the same controller which then based on the value and determines what to do?
Same thing with {action}, if the {action} portion of my url is also dynamic, is there a way to program this scenario?
Absolutely! You'll need to override the DefaultControllerFactory to find a custom controller if one doesn't exist. Then you'll need to write an IActionInvoker to handle dynamic action names.
Your controller factory will look something like:
public class DynamicControllerFactory : DefaultControllerFactory
{
private readonly IServiceLocator _Locator;
public DynamicControllerFactory(IServiceLocator locator)
{
_Locator = locator;
}
protected override Type GetControllerType(string controllerName)
{
var controllerType = base.GetControllerType(controllerName);
// if a controller wasn't found with a matching name, return our dynamic controller
return controllerType ?? typeof (DynamicController);
}
protected override IController GetControllerInstance(Type controllerType)
{
var controller = base.GetControllerInstance(controllerType) as Controller;
var actionInvoker = _Locator.GetInstance<IActionInvoker>();
if (actionInvoker != null)
{
controller.ActionInvoker = actionInvoker;
}
return controller;
}
}
Then your action invoker would be like:
public class DynamicActionInvoker : ControllerActionInvoker
{
private readonly IServiceLocator _Locator;
public DynamicActionInvoker(IServiceLocator locator)
{
_Locator = locator;
}
protected override ActionDescriptor FindAction(ControllerContext controllerContext,
ControllerDescriptor controllerDescriptor, string actionName)
{
// try to match an existing action name first
var action = base.FindAction(controllerContext, controllerDescriptor, actionName);
if (action != null)
{
return action;
}
// #ray247 The remainder of this you'd probably write on your own...
var actionFinders = _Locator.GetAllInstances<IFindAction>();
if (actionFinders == null)
{
return null;
}
return actionFinders
.Select(f => f.FindAction(controllerContext, controllerDescriptor, actionName))
.Where(d => d != null)
.FirstOrDefault();
}
}
You can see a lot more of this code here. It's an old first draft attempt by myself and a coworker at writing a fully dynamic MVC pipeline. You're free to use it as a reference and copy what you want.
Edit
I figured I should include some background about what that code does. We were trying to dynamically build the MVC layer around a domain model. So if your domain contained a Product class, you could navigate to products\alls to see a list of all products. If you wanted to add a product, you'd navigate to product\add. You could go to product\edit\1 to edit a product. We even tried things like allowing you to edit properties on an entity. So product\editprice\1?value=42 would set the price property of product #1 to 42. (My paths might be a little off, I can't recall the exact syntax anymore.) Hope this helps!
After a little more reflection, there may be a bit simpler way for you to handle the dynamic action names than my other answer. You'll still need to override the default controller factory. I think you could define your route like:
routes.MapRoute("Dynamic", "{controller}/{command}/{id}", new { action = "ProcessCommand" });
Then on your default/dynamic controller you'd have
public ActionResult ProcessCommand(string command, int id)
{
switch(command)
{
// whatever.
}
}
You need to write your own IControllerFactory (or perhaps derive from DefaultControllerFactory) and then register it with ControllerBuilder.
Iam working with it in .Core but i'll share it's MVC version for all, after that i will share the core version
case OwnerType.DynamicPage:
var dp = mediator.Handle(new Domain.DynamicPages.DynamicPageDtoQuery { ShopId = ShopId, SeoId = seoSearchDto.Id }.AsSingle());
if (dp != null)
{
return GetDynamicPage(dp.Id);
}
break;
// some codes
private ActionResult GetDynamicPage(int id)
{
var routeObj = new
{
action = "Detail",
controller = "DynamicPage",
id = id
};
var bController = DependencyResolver.Current.GetService<DynamicPageController>();
SetControllerContext(bController, routeObj);
return bController.Detail(id);
}
// and
private void SetControllerContext(ControllerBase controller, object routeObj)
{
RouteValueDictionary routeValues = new RouteValueDictionary(routeObj);
var vpd = RouteTable.Routes["Default"].GetVirtualPath(this.ControllerContext.RequestContext, routeValues);
RouteData routeData = new RouteData();
foreach (KeyValuePair<string, object> kvp in routeValues)
{
routeData.Values.Add(kvp.Key, kvp.Value);
}
foreach (KeyValuePair<string, object> kvp in vpd.DataTokens)
{
routeData.DataTokens.Add(kvp.Key, kvp.Value);
}
routeData.Route = vpd.Route;
if (routeData.RouteHandler == null)
routeData.RouteHandler = new MvcRouteHandler();
controller.ControllerContext = new ControllerContext(this.ControllerContext.HttpContext, routeData, controller);
}