RIA Services and MVVM loading, a question about querying data (separating data) - silverlight

First of all, sorry for the bad title, I can only describe the problem
Let's say the database on the server has a table/type called Tasks
and these tasks can be owned by a user and assigned to a user.
SomeTask.Owner = SomeUser
SomeTask.Assignee = SomeOtherUser
In the server some additional queries are defined:
public IQueryable<Task> GetAssignedTasks(int UserId) { /* gets the assigned tasks */ };
public IQueryable<Task> GetOwnedTasks(int UserId) { /* gets the owned tasks */ };
In the ViewModel these could be loaded as such:
var ownedTasksQuery = context.GetOwnedTasksQuery(userId);
context.Load(ownedTasksQuery);
var assignedTasksQuery = context.GetAssignedTasksQuery(userId);
context.Load(assignedTasksQuery);
The problem here is that both results get loaded into the context,
ie, context.Tasks contains the union of both query results
My first thought here was to simply change the getter for the properties in my VieWModel:
public IEnumerable<Task> OwnedTasks
{
get { return context.Tasks.Where(t => t.UserId == userId); }
}
public IEnumerable<Task> AssignedTasks
{
get { return context.Tasks.Where(t => t.UserId == userId); }
}
However, when I bind the view to these properties, nothing is returned,
whereas if I where to use the following, all loaded records are returned (obviously):
public IEnumerable<Task> OwnedTasks
{
get { return context.Tasks; }
}
public IEnumerable<Task> AssignedTasks
{
get { return context.Tasks; }
}
I'm guessing I'm going about this completely the wrong way,
what's the correct way to handle a situation like this?
Update: Or should I simply handle this by creating another instance of the context?
Update: Seems I was going at this the wrong way...
I'm still thinking in terms of classing database queries...
All I have to do to solve my problem here is load the User including the assigned and owned tasks...
ObjectContext.Users.Include("OwnedTasks").Include("AssignedTasks")
Will make this community wiki, in case somebody else does the same thing.

Did you mean to put Where(t => t.UserId = userId) or did you mean:
Where(t => t.UserId == userId);
i.e. you used an assignment operator instead of compare. (LINQ converts "==" to "=" in SQL behind the scenes, but you must use C# operators).

Related

Get subset of rows from table and filter after the query is executed

I am using entity framework core with models for all the tables in the SQL database.
I have a linq query that pulls rows from a table - let's call it facilities.
Then, I iterate the results of the query using fornext() (don't ask) :)
Within the loop we pull data from various other tables that are related to facilities.
Obviously this is a huge performance issue since there can be up to 100 rows in facilities which means the DB gets queried every time the loop iterates for every additional table we are pulling from. Note that some of the tables are from another database and you cannot join between contexts. Already tried that.
So, I thought to myself, let's pull all the rows from the related tables before we process the loop. That way, we only make those db calls one time for each associated table.
var pracloc = _ODSContext.AllPractitionerLocations
.Where(l => l.AllPractitionerLocationID != 0);
And, that works just fine.
Next step, let's simplify the code and pull some of those db calls out into private methods within the class.
For example:
Here's where I call the method (this replaces the line above).
var pracloc = GetAllPractitionerLocationsDTO();
Here's the method.
private AllPractitionerLocationsDTO GetAllPractitionerLocationsDTO()
{
AllPractitionerLocationsDTO dto = new();
dto.MyList = new List<AllPractitionerLocationDTO>();
var myo = _ODSContext.AllPractitionerLocations
.Where(s => s.AllPractitionerLocationID != 0)
.Select(g => new AllPractitionerLocationDTO()
{
AllPractitionerLocationID = g.AllPractitionerLocationID
});
dto.MyList = myo.ToList();
return dto;
}
Here's the subsequent filter (which is unchanged between the two data queries above):
var PracLocation = pracloc
.Where(a => a.LocationID = provider.LocationID)
.FirstOrDefault();
And, this works fine as long as I pull the data by querying the DB directly as in the first line above.
When I try to pull the data in the method, the line above throws:
'AllPractitionerLocationsDTO' does not contain a definition for 'Where' and no accessible extension method 'Where' accepting a first argument of type 'AllPractitionerLocationsDTO' could be found (are you missing a using directive or an assembly reference?)
AllPractitionerLocationsDTO is a model class with a subset of the rows in the "real" model:
public class AllPractitionerLocationDTO
{
public int SRCAllPractitionerLocationID { get; set; }
public int AllPractitionerLocationID { get; set; }
}
public class AllPractitionerLocationsDTO
{
public List<AllPractitionerLocationDTO> MyList;
}
Since that is identical in structure to the actual DB table, why won't the where clause work? Or, how can I implement my own where within the model class?
I even tried adding the dbset<> to the context. Still didn't work
public virtual DbSet<AllPractitionerLocationDTO> AllPractitionerLocationDTOs { get; set; }
Help me please.
You have to return IQueryable from your method. Only in this case you can reuse it later and filter effectively:
private IQueryable<AllPractitionerLocationDTO> GetAllPractitionerLocationsDTO()
{
var query = _ODSContext.AllPractitionerLocations
.Where(s => s.AllPractitionerLocationID != 0)
.Select(g => new AllPractitionerLocationDTO
{
AllPractitionerLocationID = g.AllPractitionerLocationID
});
return query;
}

creating a simple back end design in Laravel for private/public ownership

I'm using Laravel for a site where most database objects can be private (i.e., viewed only by their owner) or public (viewed by everyone, including guests). Each of these has a user_id, which I set to NULL when the object is public.
What's the simplest way of authenticating routes for this scenario? For example, in /routes/web.php I have:
Route::get('/{tournament}/players', [TournamentController::class, 'indexPlayers']);
and I want to make sure that tournament->user_id is either NULL or corresponds to the user's id. I was able to do this by explicitly binding tournament in /app/Providers/RouteServiceProvider.php:
Route::bind('tournament', function ($hash) {
$user_id = Auth::user()->id ?? NULL;
return Tournament::where([['hash', $hash], ['user_id', $user_id]])
->orWhere([['hash', $hash], ['user_id', NULL]])
->firstOrFail();
});
but I have the strong feeling that I'm making it too complicated or doing things in the wrong place. Is there a better way? Should I by doing this inside TournamentController, for example?
First, there is now the syntax Auth::id() that can be used as a shorthand for Auth::user()->id ?? NULL, so that saves some trouble.
Next, I ended up moving the logic out of RouteServiceProvider.php and into the controller, so that I can explicitly control what happens for public vs. private objects:
class TournamentController extends Controller
{
public function indexPlayers(Tournament $tournament)
{
if ($tournament->user_id === NULL) {
// public
} else if ($tournament->user_id === Auth::id()) {
// private
} else {
// unauthorized
}
}
...
}

Using stored procedures (Linq-to-SQL, not EF) in WCF RIA - Silverlight 4

For the love of heaven and earth I really wish someone could help me out with this issue. It seems everyone has something to say about EF but nothing about Linq-to-SQL.
I am trying to grab some data from my table via a stored procedure, believe me, that's all.
I added the Linq-to-SQL model (LAMP.dbml)
added the stored procedure (getAffectedParcel) from the server explorer. getAffectedParcel takes 2 strings as parameters
Build the application.
Added a domain service class (LAMPService)
Selected the (LAMPDataContext) as the data context class (normally I would tick generate metadata, but since I am not working with tables it's not enabled for ticking)
Added the following function to the LAMPService.cs:
public IEnumerable < getAffectedParcelResult > GetTheAffectedParcels(String v, String vf)
{
return this.DataContext.getAffectedParcel(v, vf).AsEnumerable();
}
Added the following code to a Silverlight page in an attempt to consume the stored procedure:
LAMPContext db = new LAMPContext();
try
{
var q = db.GetTheAffectedParcels("18606004005", "").Value;
foreach (getAffectedParcelResult GAP in q)
{
MessageBox.Show(GAP.Owner);
}
}
catch (Exception ex)
{
MessageBox.Show (ex.Message.ToString());
}
Build and run application. An error occurs stating:
Object reference not set to an instance of an object.
I have tried ~1000,000 ways to see if this thing would work, but to no avail. Please don't tell me to use Entity Framework, I want to use Linq-to-SQL. Can someone (anyone) help me out here.
//houdini
Calling a stored procedure from the Silverlight client happens in the Async world. Let's consider an example from the AdventureWorks database...
Here's what the Domain Service method looks like. It is calling the EF on a stored procedure in the database called 'BillOfMaterials'.
public IQueryable<BillOfMaterial> GetBillOfMaterials()
{
return this.ObjectContext.BillOfMaterials;
}
Back on the client side, here is the code for setting up the call...
public GetSp()
{
InitializeComponent();
DomainService1 ds1 = new DomainService1();
var lo = ds1.Load(ds1.GetBillOfMaterialsQuery());
lo.Completed += LoCompleted;
}
First, the Domain Service is created, and then it is used to load the results of the stored procedure. In this particular case, the result of this is an instance of 'LoadOperation'. These things are async, so the LoadOperation needs to have a callback for when it is finished. The callback code looks like this...
public ObservableCollection<BillOfMaterial> MyList { get; set; }
void LoCompleted(object sender, EventArgs e)
{
LoadOperation lo = sender as LoadOperation;
if(lo!=null)
{
MyList = new ObservableCollection<BillOfMaterial>();
foreach(BillOfMaterial bi in lo.AllEntities)
{
MyList.Add(bi);
}
dataGrid1.ItemsSource = MyList;
}
}
In this method, the 'sender' is dereferenced into the LoadOperation instance, and then all the goodies from the database can be accessed. In this trivial example, a list is built and passed to DataGrid as the ItemsSource. It's good for understanding, but you would probably do something else in practice.
That should solve your problem. :)
The best advice I can give on Silverlight and RIA is never do ANYTHING on your own until you have tried it in AdventureWorks. You will just waste your time and beat your head against the wall.
Firstly, it seems like your DomainService code is written for Invoke() rather than Query(). You should use Query as it enables you to update data back to the server.
Solution: you should add a [Query] attribute to GetTheAffectedParcels on the domain service.
[Query]
public IQueryable<Parcel>
GetTheAffectedParcels(string ParcelNumber, string LotNumber)
{
// etc.
}
Secondly, RIA Services needs to know which is the primary key on the Parcel class.
Solution: Apply a MetadataType attribute to the Parcel class, which allows you to add metadata to the Parcel class indirectly, since it is generated by Linq2Sql and you couldn't add annotations directly to the ParcelId - it'd get wiped away.
[MetadataType(typeof(ParcelMetadata)]
public partial class Parcel
{
}
public class ParcelMetadata
{
[System.ComponentModel.DataAnnotations.Key]
public int ParcelId {get; set; }
}
Thirdly, modify your client like this. Instead try this on the Silverlight client:
LAMPContext db = new LAMPContext();
try
{
var q = db.GetTheAffectedParcelsQuery("18606004005", "");
db.Load(q, (op) =>
{
if (op.HasError)
{
label1.Text = op.Error.Message;
op.MarkErrorAsHandled();
}
else
{
foreach (var parcel in op.Entities)
{
// your code here
}
}
}
}
catch (Exception ex)
{
label1.Text = op.ex.Message;
}
Much thanks to Chui and Garry who practically kicked me in the right direction :) [thanks guys...ouch]
This is the procedure I finally undertook:
-After adding the data model(LINQ2SQL) and the domain service, I created a partial class [as suggested by Chui] and included the following metadata info therein:
[MetadataTypeAttribute(typeof(getAffectedParcelResult.getAffectedParcelResultMetadata))]
public partial class getAffectedParcelResult
{
internal sealed class getAffectedParcelResultMetadata
{
[Key]
public string PENumber { get; set; }
}
}
Then, Adjusted the Domain Service to include the following:
[Query]
public IQueryable<getAffectedParcelResult> GetTheAffectedParcels(string v, string vf)
{
// IEnumerable<getAffectedParcelResult> ap = this.DataContext.getAffectedParcel(v, vf);
return this.DataContext.getAffectedParcel(v, vf).AsQueryable();
}
Then Build the app, afterwhich the getAffectedParcelResult store procedure appeared in the Data Sources panel. I wanted to access this via code however. Therefore, I accessed it in silverlight [.xaml page] via the following:
LAMPContext db = new LAMPContext();
var q = db.GetTheAffectedParcelsQuery("18606004005", "");
db.Load(q, (op) =>
{
if (op.HasError)
{
MessageBox.Show(op.Error.Message);
op.MarkErrorAsHandled();
}
else
{
foreach (getAffectedParcelResult gap in op.Entities)
{
ownerTextBlock.Text = gap.Owner.ToString();
}
}
},false);
This worked nicely. The thing is, my stored procedure returns a complex type so to speak. As of such, it was not possible to map it to any particular entity.
Oh and by the way this article helped out as well:
http://onmick.com/Home/tabid/154/articleType/ArticleView/articleId/2/Pulling-Data-from-Stored-Procedures-in-WCF-RIA-Services-for-Silverlight.aspx

Linq to Sql - How to update an object using a repository pattern?

There is tons of information on this, but even after reading for hours and hours I can't seem to get this to work the way I want.
I'm trying to update a User object by passing in a User object and generically comparing changes to a User object I pull out of the database. I always end up getting the NotSupportedException when using this method:
An attempt has been made to Attach or
Add an entity that is not new, perhaps
having been loaded from another
DataContext. This is not supported.
Here is how I am trying to do it:
public void SaveUser(User User)
{
using (DataContext dataContext = new DataContext(WebConfigurationManager.ConnectionStrings["database"].ConnectionString))
{
// New user
if (User.UserID == 0)
{
dataContext.Users.InsertOnSubmit(User);
}
// Existing user
else
{
User dbUser = dataContext.Users.Single(u => u.UserID.Equals(User.UserID));
Type t = dbUser.GetType();
foreach (PropertyInfo p in t.GetProperties())
{
if (p.CanWrite & p.GetValue(dbUser, null) != p.GetValue(User, null))
{
p.SetValue(dbUser, p.GetValue(User, null), null);
}
}
//dataContext.Refresh(RefreshMode.KeepCurrentValues, dbUser);
}
dataContext.SubmitChanges();
}
}
The commented out line I tried uncommented too, but it was no help.
If I comment out the foreach() loop and add a line like dbUser.UserName = "Cheese"; it will update the User's name in the database fine. That leads me to believe it is something with how the foreach() loop changing the dbUser object that causes this to fail.
When I debug the dbUser object, it appears to correctly acquire all the changes from the User object that was passed as an argument.
I also did some reading on optimistic concurrency and added a column to the table of data type timestamp, but that didn't seem to have any effect either.
What exactly am I doing wrong here?
How can I get this to generically detect what has changed and correctly persist the changes to the database?
My guess is there's a foreign key relation that you are trying to copy over that was not initially loaded (because of lazy-loading) During the copying, it's attempting to load it, but the DataContext has already been disposed.
I've been working on a similar problem. I ended up using AutoMapper to handle copying the properties for me. I have configured AutoMapper to ignore the primary key field as well as any relations. Something like:
public void Update(User user)
{
using (var db = new DataContext(...))
{
var userFromDb = db.Users.Where(x => x.Id == user.Id).Single();
AutoMapper.Mapper.Map(user, userFromDb);
db.SubmitChanges();
}
}
My automapper configuration is something like
AutoMapper.Mapper.Create<User, User>().ForMember(dest => dest.Id, opt => opt.Ignore())
.ForMember(dest => dest.SomeRelation, opt => opt.Ignore());
You can find AutoMapper here: http://automapper.codeplex.com/
I keep my repo pretty lean, it's only job is to interact with the database. I build a Service layer on top of the repo that does a little more work
public class EventRepository : IEventRepository
{
private DBDataContext dc;
public EventRepository()
{
dc = new DBDataContext();
}
public void Create(Event #event)
{
dc.Events.InsertOnSubmit(#event);
}
public System.Linq.IQueryable<Event> Read()
{
object events = (from e in dc.Eventse);
return events.AsQueryable;
}
public void SubmitChanges()
{
dc.SubmitChanges();
}
}
Then the corresponding call from the service layer looks like this
public void AddEvent(Event #event)
{
_EventRepository.Create(#event);
}
public void SubmitChanges()
{
_EventRepository.SubmitChanges();
}
And I call it from my controller.
// AutoMapper will allow us to map the ViewModel with the DomainModel
Mapper.CreateMap<Domain.ViewModels.EventsAddViewModel, Domain.Event>();
object #event = Mapper.Map<Domain.ViewModels.EventsAddViewModel, Domain.Event>(eventToAdd);
// Add the event to the database
EventService.AddEvent(#event);
EventService.SubmitChanges();

How to achieve "Blendability" when using DataServiceCollection in my ViewModel

I'm looking at using oData endpoints in my Silverlight client. Naturally, I'm doing MVVM and I want the project to be nice and "Blendable" (i.e. I must be able to cleanly use static data instead of the oData endpoints when in design mode.)
Now to the problem. I'd like to use the DataServiceCollection in my ViewModels, since it allows for nice bindable collections without having to worry too much with BeginExecute/EndExecute etc.
Now, let's look at some code. My Model interface looks like this:
public interface ITasksModel
{
IQueryable<Task> Tasks { get; }
}
The oData endpoint implementation of that interface:
public class TasksModel : ITasksModel
{
Uri svcUri = new Uri("http://localhost:2404/Services/TasksDataService.svc");
TaskModelContainer _container;
public TasksModel()
{
_container = new TaskModelContainer(svcUri);
}
public IQueryable<Task> Tasks
{
get
{
return _container.TaskSet;
}
}
}
And the "Blendable" design-time implementation:
public class DesignModeTasksModel : ITasksModel
{
private List<Task> _taskCollection = new List<Task>();
public DesignModeTasksModel()
{
_taskCollection.Add(new Task() { Id = 1, Title = "Task 1" });
_taskCollection.Add(new Task() { Id = 2, Title = "Task 2" });
_taskCollection.Add(new Task() { Id = 3, Title = "Task 3" });
}
public IQueryable<Task> Tasks
{
get {
return _taskCollection.AsQueryable();
}
}
}
However, when I try to use this last one in my ViewModel constructor:
public TaskListViewModel(ITasksModel tasksModel)
{
_tasksModel = tasksModel;
_tasks = new DataServiceCollection<Task>();
_tasks.LoadAsync(_tasksModel.Tasks);
}
I get an exception:
Only a typed DataServiceQuery object can be supplied when calling the LoadAsync method on DataServiceCollection.
First of all, if this is the case, why not make the input parameter of LoadAsync be typed as DataServiceQuery?
Second, what is the "proper" way of doing what I'm trying to accomplish?
The reason LoadAsync requires DataServiceQuery is that just plain IQueryable doesn't define asynchronous way of executing the query. The reason the method takes IQueryable type as its parameter is so that users don't have to cast the query object to DataServiceQuery explicitely (makes the code shorter) and since we assume that users will try to run their code at least once, they would see the error immediately (as you did).
LoadAsync only supports asynchronous operations, so it needs the DataServiceQuery. If you already have the results (without a need to execute async request) you can call the Load method instead. Which is the answer to your second question. Instead of calling LoadAsync for both desing time and run time, you could use Load for design time and LoadAsync for run time. But due to tracking constrains you might need to create the DataServiceCollection in different way.
Something like this:
DataServiceCollection<Task> dsc;
DataServiceQuery<Task> dsq = _tasksModel as DataServiceQuery<Task>;
if (dsq != null)
{
dsc = new DataServiceCollection<Task>();
dsc.LoadAsync(dsq);
}
else
{
dsc = new DataServiceCollection<Task>(myDataServiceContext);
dsc.Load(_tasksModel);
// Invoke the LoadAsyncCompleted handler here
}
If you pass the DataServiceContext to the constructor before caling Load the entities will be tracked (just like in the LoadAsync case). If you don't need that you can call the constructor which takes IEnumerable and TrackingMode and turn off tracking on it.

Resources