I've looked all over and haven't been able to find a clear answer to a seemingly common question: How can I do two-way databinding over a many-to-many relationship in ASP.net?
I have the following database structure:
I am currently writing a page for editing or adding a User record. Databinding things such as name and password is simple, but what I really need it to be able to display a list of all PhoneGroups and choose one or more from the list. How do I do this?
I tried a CheckBoxList, but while I can display the list of PhoneGroups, How do I bind the Checked state of each box based on whether the user has access? Some solutions use a loop in the OnDataBound event of the CheckBoxList. If I do this, how do I update the database when the checked state changes? I could go the brute force approach and write code to do this, but isn't there something that can make this simpler? It seems like such a common scenario.
Update #1
I am currently using Devart's LinqConnect, but I am open to change. The backend database is MySQL.
Yeah it is a common scenario and binding to that event is the solution i see used.
It is fairly simple when you consdier what the code is doing int he background. You could write your own custom server control, but thats a lot more difficult.
MVC may offer you an alternative ...
really why not redesign and only return the objects that they ahve permission for?
as for updating items in the database you need to say more about the architecture. But ultimatley to update an item you have to take the new item ... you have to do womthing like this
public void StoreTheUpdatedData(YourBusinessObject theBusinessObject)
{
var yourDataContext = new DataContext()
var oldObject = (from i in yourDataContext.YourbusinessObjects
where (blah equals blah to select your item and only your item)
select i).First();
//repeat for all properties in the object
oldObject.Property = theBusinessObject.Property;
yourDataContext.SaveChanges();
}
code liek that is what you need to do the update.
the save method varies depending on which ORM you are using ... I think linq2SSql uses commitChanges for instance. Been a while since i used that one.
Related
I want to create a report which will have only one page, say information about only one customer. It should be noted that I'll always have to show information about one customer at one time in a report. That means, there is no scenario where I'll be passing collection of more than one customer.
For now, I've used Data Source and bind it with the report. Though I need to pass collection as a ReportDataSource, that collection will always have one item.
So, is this a correct approach? Shall I use ReportParameter instead of having Data Source? Is there any performance drawback of using DataSource? Is there any performance advantage of using ReportParameter?
Also, using DataSource gives me a liberty of writing a framework which can take inputs as report and collection, and then I can easily generate a report. In case of ReportParameter, I'll need to use reflection to loop through every property and then create ReportParameter.
Please suggest the recommended approach for this scenario.
A good business practice is to have a parameter where you specify which customer you want to report on. In your data source, you simply include the parameter in your WHERE clause. For SQL Server it would look something like this:
WHERE CUSTID = #CustomerParameter
Your report is just a template to display the results of that query for the selected customer.
I have added a LinkField called Website to a content type using a part with the same name as the content type.
ContentDefinitionManager.AlterTypeDefinition("MyContentType", a => a
.WithPart("CommonPart")
.WithPart("MyContentType")
.Creatable());
ContentDefinitionManager.AlterPartDefinition("MyContentType", cft => cft
.WithField("Website", a => a.OfType("LinkField").WithDisplayName("Website")
.WithSetting("FieldIndexing.Included", "True"))
.Attachable());
I then create some default content items during the migration.
I'm creating the item before adding the field data because I have had problems with fields not being updated when their values are set before the item is created. (Feel free to shine some light on that, but that isn't my question though)
var myItem = _orchardServices.ContentManager.New("MyContentType");
_orchardServices.ContentManager.Create(myItem);
var websitePart = myItem.Parts.FirstOrDefault(x => x.Fields.Any(y => y.Name == "Website"));
var websiteLinkField = websitePart .Fields.FirstOrDefault(x => x.Name == "Website") as LinkField;
websiteLinkField.Value = "http://www.google.com";
websiteLinkField.Text = "Link to google";
_orchardServices.ContentManager.Publish(myItem);
I realize there are more dynamic ways to access the field, but this seems to work too.
The data shows up when I view the items, but then I move on to making a Query.
I use the UI to build a simple query looking for the word "google" in the text of the LinkField, then I hit preview.
No results are found.
I then open up one of the items created from the migration and simply hit the "Save" button.
Then I try the preview again and the item I saved now shows up.
So as far as I can tell something is happening when I save a content item that I'm not doing from the migration. But I have been stepping through the code going over all angles, and I just can't find it.
I suspect maybe some handler is supposed to get triggered in order to create the FieldIndex'es ?
(I know how to trigger an update for the Lucene index, but as one would expect it does not affect querying fields using the Projections module and I'm really lost at this point.)
By now I'm just stabbing blindly in the dark.
Would really appreciate any help I can get, even if it's just something pointing me back in the right direction. Thank you.
You should change
_orchardServices.ContentManager.Create(myItem);
to
_orchardServices.ContentManager.Create(myItem, Orchard.ContentManagement.VersionOptions.Draft);
For understanding look at CreatePOST method of Orchard.Core.Contents.Controllers.AdminController class and Publish method of Orchard.ContentManagement.DefaultContentManager class
In your case when you call a Create(myItem) then created published content item and all handlers are invoked normally (but has not yet set up a desired data). When you call Publish(myItem) nothing happens (no handlers are invoked) because your content is already published.
I've raised this as a bug, vote for it if you think it needs fixed.
#Alexander Petryakov is correct in his description of what is happening and his work around is probably the correct approach, however the behaviour doesn't make sense, which is why I have raised the bug. The code in your question manages to create an inconsistency between the content view of the data, stored in the Orchard_Framework_ContentItemVersionRecord table and the Projections view of the data stored in the Orchard_Projections_StringFieldIndexRecord table. Essentially, the Orchard_Projections_StringFieldIndexRecord contains null because it hasn't processed the publish event after you updated the field.
The code you have essentially does the following things:
Create a content item + publish it's creation
Update one of the content items fields this update doesn't change the state of the content
Try to publish the content item which doesn't do anything because it thinks it is already published.
To me, if you update a field on the content item, then the state of the item you are working on should no longer be published (it's changed since you published it). The Fields provide hooks that allow you to be notified when they are updated, so an alternate way of solving the problem would be to create a class that implements the interface IFieldStorageEvents that updates the published state of the content when a field is updated.
public class FieldUpdateEventHandler : IFieldStorageEvents {
public void SetCalled(FieldStorageEventContext context) {
context.Content.ContentItem.VersionRecord.Published = false;
}
}
This would allow your original code to run as it was written.
I've been looking into how best to do this and wisdom would be appreciated. For read only purposes, I've been happily using LINQ and binding it to a grid. For editing purposes, I've used the LinqDataSource control, enabled the Edit/Delete operations in the process, and I have a nice editable grid bound to some or all of the table's fields.
Now I have a situation where I want to edit a few fields in table A, but there are various values in linked table B that I want to display in that grid too (no editing of those). So my query looks like the below. The fields in tblDupes (cleared, notes) are what I want to edit, but I'd like to display those tblVoucher ones.
var theDupes = from d in db.tblDupes
where d.dupeGroup == Ref
select new
{
Ref = d.dupeGroup,
InvoiceNum = d.tblVoucher.invoiceRef,
Value = d.tblVoucher.invoiceAmtDecimal,
VendorNum = d.tblVoucher.vendorID,
VendorName = d.tblVoucher.vendorName,
Cleared = d.Cleared
Notes = d.Notes
};
A similar but different question LINQDataSource - Query Multiple Tables? sent me looking at scott Guthrie's blog entry http://weblogs.asp.net/scottgu/archive/2007/09/07/linq-to-sql-part-9-using-a-custom-linq-expression-with-the-lt-asp-linqdatasource-gt-control.aspx, where he handles various events to have a LinqDataSource with a custom query across tables. This still seems aimed at explicitly designed classes though, even if the class has only a subset of the fields.
So my question is: is there an easy way to allow committing of the changes made to the anonymous collection (a changes.Submit type action), or just an easy way to 'display' fields from another table while not involving them in the updating?
EDIT: Thinking more, it doesn't have to be anonymous really. I'd be happy to define a class to contain the elements in that query, since it won't change often. But, those elements would be across two tables, even though only one needs updating. Not sure if that suggests entity framework would be more suitable - I get the feeling it wouldn't - I don't want the whole 'model' always grouping the fields in this way.
Thanks!
Taking a wild guess here, but couldn't you listen to the LINQDataSource.Updating event and perform your save there? You would, of course, have some problems with the mapping since you cannot type the object in the LinqDataSourceUpdateEventArgs.OriginalObject.
What if you create a ViewModel instead of the anonymous type. Something like DupeVoucherViewModel.
Then in the Updating event, you could cast the LinqDataSourceUpdateEventArgs.OriginalObject to the DupeVoucherViewModel object and start mapping and saving your data.
Example
Given that you create a view model (a class) that you call DupeVoucherViewModel and bind to that like so:
var theDupes = from d in db.tblDupes
where d.dupeGroup == Ref
select new DupeVoucherViewModel
{
Ref = d.dupeGroup,
InvoiceNum = d.tblVoucher.invoiceRef,
Value = d.tblVoucher.invoiceAmtDecimal,
VendorNum = d.tblVoucher.vendorID,
VendorName = d.tblVoucher.vendorName,
Cleared = d.Cleared
Notes = d.Notes
};
Then the server tag should map the updating event like so:
<asp:LinqDataSource EnableUpdate="true" OnUpdating="LinqDataSource_Updating" />
And the code behind should contain the following method:
protected void LinqDataSource_Updating(object sender, LinqDataSourceUpdateEventArgs e)
{
// originalObject contains the unchanged object
var originalObject = (DupeVoucherViewModel)e.OriginalObject;
// newObject contains the changed object
var newObject = (DupeVoucherViewModel)e.NewObject;
// Perform your save using newObject
yourDataAccessComponent.SaveSomething(newObject);
}
You might need to include some more information in the DupeVoucherViewModel such as an ID of tblDupes or something, so that you can load that and change it.
You can read more about the LinqDataSource here: http://msdn.microsoft.com/en-us/library/bb514963.aspx
I've created two classes in business layer.
the first one is called Users with id (int), pass (string) and privileges (Privilege) properties and the second one is called Privilege and has id (int) and privilegeName (string) properties.
I've a method that returns all the users, and I use a repeater (actually I bind it to a DataList to auto create the ItemTemplate for me and then use a repeater) and it works and displays all the properties well except for my List property. it generates instead something like this System.Collections.Generic.List`1[WebApplication2.Public.BLL.Users]
I want to display it in a friendly way like "User Privileges : Privi1, Privi2" but still I want to keep the layers of my application clean and structured, for example I won't store them in a database in the same table and just store them as a text and append it.
I hope to find a simple and good solution...Thanks in advance guys =)
PS : I don't want to display the object Privilege, I want to display privilege.privilegeName
When using repeaters, there are two approaches, one is the one suggested by Bugai13: to have a custom property that displays it. This is fine for certain types of nested data.
Your other option is to just have a repeater inside a repeater, and bind it appropriately (to what would be a list assigned to your main data object, depending on how you O/R Mapper works).
You can have the code for the custom display property not in the data model, but in your presentation layer somewhere (depending on your framework/design), so it's not a "bad" thing to do that. It's up to you, with whatever "feels" best.
Just create property at your Bussiness object, and bind it:
public string PrivilegiesString
{
get
{
var sb = new StringBuilder("User Privileges : ");
foreach(var item in privileges)
{
sb.AppendFormat("{0}, ",item.privilegeName);
}
return sb.ToString();
}
}
I am currently trying to bind an entity to a form however I want to have DataConfidenceLevel (see below) bound to a combobox with ConfidenceDescription as the display member. What is the correct way to populate the combobox?
(I am currently using WPF but a Winforms answer is acceptable)
Thanks
Entity Designer http://img19.imageshack.us/img19/374/entity.png
You want to bind a collection to a control and have a releated entity - namely navigation property DataConfidenceLevel of type DataConfidenceLevel - as the display member?
That is usually achieved really simple by overriding ToString(),
public partial class DataConfidenceLevel
{
public override String ToString()
{
return this.ConfidenceDescription;
}
}
and than setting DisplayMember to the DataConfidenceLevel property of the entity you want to bind.
The answer was simpler than I was expecting.
comboBox.DataBindings.Add(new Binding("SelectedItem", this.dataBindingSource, "DataConfidenceLevel", true));
comboBox.DataSource = db.DataConfidenceLevel;
comboBox.DisplayMember = "ConfidenceDescription";
comboBox.ValueMember = "ConfidenceLevelID";
I wrote two blog entries about one approach to handling this situation - it applies to ASP.net, but it might help you out.
Here are the posts, the first one is more of an introduction to the problem, the second entry shows how to pin it all together.
I'm not sure whether this qualifies as "the correct way" but it's certainly an approach :) I'd be happy to hear back if this helps you out!
Edit: After reading danbruc's answer, you can certainly override ToString on the Navigation property as he has suggested (for read only), but that's only a partial answer.
This won't work unless your LINQ query contains the "Include" statement, e.g.
var listOfThings = (from t in db.Thingy
.Include("DataConfidenceLevel")
select t).ToList();
Omitting the .Include() means that nothing will get bound to the column.