The problem am facing is, anytime I call events.Tags.Add(tag) and call Save changes on the context, it ends up creating a new tag info in the Tags table instead of just inserting the EventId and TagId into just the EventTags Table.
Base on the data below how do I add an event and tag into the EventTags Table. Lets say I want to add Event with Id=2 and and tag with Id =1 to the EventTags table.
I have the following entities.
public class Event
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Tag> Tags { get; set; }
}
public class Tag
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Event> Events { get; set; }
}
public class EventConfiguration : EntityTypeConfiguration<Event> {
public EventConfiguration () {
ToTable("Events");
HasKey(x => x.Id).Property(x => x.Id).HasColumnName("Id").IsRequired();
Property(x => x.Name).HasColumnName("Name").IsRequired();
HasMany(x => x.Tags)
.WithMany(x => x.Events)
.Map(m => {
m.ToTable("EventTags");
m.MapLeftKey("EventId");
m.MapRightKey("TagId");
});
}
}
public class TagConfiguration : EntityTypeConfiguration<Tag> {
public TagConfiguration () {
ToTable("Tags");
HasKey(x => x.Id).Property(x => x.Id).HasColumnName("Id").IsRequired();
Property(x => x.Name).HasColumnName("Name").IsRequired();
}
}
/*
These are the records in my many to many tables
--------------
Events Table
--------------
Id Name
1 Test1
2 Test2
--------------
EventTags
-------------
EventId TagId
1 2
-------------
Tags
------------
Id Name
1 MVC
2 C#
*/
Lets say I want to add Event with Id=2 and and tag with Id =1 to the
EventTags table.
using (var context = new MyContext())
{
// Load the event
var theEvent = context.Events.Find(2);
// Load the tag
var theTag = context.Tags.Find(1);
// Add tag to Tags collection of the event
theEvent.Tags.Add(theTag);
// Save
context.SaveChanges();
}
It's enough to update one of the two collections. EF takes care of the other collection automatically.
Edit
Another option without loading the entities from the DB if you know the existing Ids:
using (var context = new MyContext())
{
// Create an event with Id 2
var theEvent = new Event { Id = 2, Tags = new HashSet<Tag>() };
// Create a tag with Id 1
var theTag = new Tag { Id = 1 };
// Attach both event and tag to the context
context.Events.Attach(theEvent);
context.Tags.Attach(theTag);
// Add tag to Tags collection of the event
theEvent.Tags.Add(theTag);
// Save
context.SaveChanges();
}
You can also mix both approaches, for example load event from DB, create and attach the tag.
Related
I have two tables:
Table 1 (Sale):
Id | CustomerId
Table 2 (SaleProduct):
Id | SaleId | Quantity
I have the following statement:
var topSales = db.Sales.Include(c => c.SaleProducts).Where(p =>p.CustomerId == id);
I want to sort the SaleProducts table, and select 5 top(ordered by quantity) based on SaleId foreign key, where CustomerId is equal with an id it receives in a controller method.
How can I access the SaleProducts properties in order to do something like this:
var orderedItems = topSales.OrderByDescending(c => c.Quantity).Take(5);
When I include the SaleProducts table I have in there all the properties but I can't accesc them like in the statement above.
Any help is appreciated
You have two options here:
First option.
Try querying SaleProduct table instead of Sale table and Include Sale:
var topSaleProducts = db.SaleProducts
.Where(m => m.Sale.CustomerId == id)
.Include(m => m.Sale)
.OrderByDescending(m => m.Quantity)
.Take(5);
Now you have top 5 sale products according to CustomerId and Sale will alse be loaded.
Second option.
You can use explicit loading to get top 5 SaleProducts after loading Sale. Remember that, you will query database twice by this way:
// Load the sale first
var topSale = db.Sales.First(m => m.CustomerId == id);
// Then load SaleProducts using Explicit loading.
db.Entry(topSale)
.Collection(m => m.SaleProducts)
.Query()
.OrderByDescending(m => m.Quantity)
.Take(5)
.Load();
You can order by when you access the SaleProducts property and use Take method to get 5 items.
var prods2 = db.Sales.Include(c => c.SaleProducts)
.Where(p =>p.CustomerId == id)
.Select(g => new
{
SaleId = g.Id,
Products = g.SaleProducts.OrderByDescending(h => h.Quantity)
.Take(5)
.Select(x => new
{
Id = x.Id,
Quantity = x.Quantity
})
}).ToList();
The above code will give you the Sales for a specific customer and for each customer it will include only the top 5 SaleProduct's (sorted by quantity)
I am projecting the results to an anonymous object ( new { }). If you have a view model, you can change the projection to that.
public class SaleVm
{
public int Id { set; get; }
public int CustomerId { set; get; }
public List<SaleProductVm> SaleProducts { set; get; }
}
public class SaleProductVm
{
public int Id { set; get; }
public int SaleId { set; get; }
public int Quantity { set; get; }
}
Simply replace the annonymous object projection with these classes
List<SaleVm> prods2 = db.Sales.Include(c => c.SaleProducts)
.Where(p =>p.CustomerId == id)
.Select(g => new SaleVm
{
SaleId = g.Id,
Products = g.SaleProducts.OrderByDescending(h => h.Quantity)
.Take(5)
.Select(x => new SaleProductVm
{
Id = x.Id,
Quantity = x.Quantity
})
}).ToList();
I'm trying to populate a Kendo dropdown using a Read function in a controller, but do so in a way that allows me to get items from one table or a view, based on a column in the view.
Ex.
public class AlternateCountry // <-- Table
{
public Guid CountryGUID { get; set; }
public string Name { get; set; }
}
public class Countries // <-- View
{
public bool hasAltName { get; set; }
public Guid GUID { get; set; }
public string Name { get; set; }
}
I want to show the "Countries" Name column value unless we have an alternate name for that country. Then it would show the Name column in the "AlternateCountry" table. Something like:
var getCountries = (from c in db.Countries
join alt in db.AlternateCountry on c.GUID equals alt.CountryGUID
where c.hasAltName == true
select new {
GUID = c.GUID,
Name = alt.Name
}).ToList();
return Json(getCountries, JsonRequestBehavior.AllowGet);
Problem is this doesn't account for when the alternate name is false, to grab "Name" from the Countries view. I can duplicate another of these blocks and change c.hasAltName == false, but how do I then combine both of these into one DataSourceResult set?
Actually, I was able to solve this with a union of two queries -- one for the ones without alternate names, one for those with alternate names:
var realCountries = (from c in db.Countries
where c.hasAltName == false
select new {
GUID = c.GUID,
Name = c.Name
}).ToList();
var fakeCountries = (from c in db.Countries
join alt in db.AlternateCountry on c.GUID equals alt.CountryGUID
where c.hasAltName == true
select new {
GUID = alt.GUID,
Name = alt.Name
}).ToList();
var allCountries = realCountries.Union(fakeCountries);
return Json(allCountries, JsonRequestBehavior.AllowGet);
I'm trying to set up a Dropdown list in Kendo UI Grid using HTML helpers.
When I click to edit the grid, the dropdown appears and I can select a value. However, this does not save in the database when I click update (though the simple string WordName field does).
I would also like the CatId value from the WordViewModel to also be displayed as a word/dropdown when you're not editing the fields.
As far as I can tell, I have nothing which links the int CatId to the GetCategories list. How do I go about connecting those two? I've read a little about column.ForeignKey, but I don't understand it. Below is all my relevant code.
My WordViewModel (which loads from a similar, slightly more complex database model)
public class WordViewModel
{
public int WordId { get; set; }
[Required]
public string WordName { get; set; }
public Nullable<int> CatId { get; set; }
}
My Category model (generated by the database)
public partial class Category
{
public Category()
{
this.Words = new HashSet<Word>();
}
public int CatId { get; set; }
public string CategoryName { get; set; }
public bool IsActive { get; set; }
public virtual ICollection<Word> Words { get; set; }
}
Here's my razor code for the grid in Index.cshtml
#(Html.Kendo().Grid<WordViewModel>
()
.Name("wordGrid")
.DataSource(dataSource => dataSource
.Ajax()
.Model(model =>
{
model.Id(word => word.WordId); // Specify the property which is the unique identifier of the model
model.Field(word => word.WordId).Editable(false); // Make the ID property not editable
})
.Read(read => read.Action("Read", "Words")) //Populate the grid with Words
.Update(update => update.Action("Update", "Words")) // Action invoked when the user saves an updated data item
)
.Editable(editable => editable.Mode(GridEditMode.InLine)) // Use inline editing mode
.Columns(columns =>
{
columns.Bound(c => c.WordName);
columns.Bound(c => c.CatId).EditorTemplateName("_CategoryDropdown"); //link to EditorTemplate with the same name
columns.Command(commands =>
{
commands.Edit(); // The "edit" command will edit and update data items
}).Title("Commands").Width(200);
})
.Filterable()
)
Editor Template _CategoryDropdown.cshtml
#(
Html.Kendo().DropDownList()
.Name("Category") //is this Name important?
.DataTextField("CategoryName")
.DataValueField("CategoryId")
.DataSource(source =>
{
source.Read(read => { read.Action("GetCategories", "Words"); });
})
.OptionLabel("Select a category")
)
My function to get the drop down list from the database.
public JsonResult GetCategories()
{
var items = db.Categories.ToList().Select(c => new Category
{
CatId = c.CatId,
CategoryName = c.CategoryName
});
return Json(items, JsonRequestBehavior.AllowGet);
}
Here is a working solution. Rather than using column.ForeignKey, I ended up manually connecting the CatId with the CategoryName and including both in the WordViewModel.
My final files:
public class WordViewModel
{
public int WordId { get; set; }
[Required]
public string WordName { get; set; }
public string CategoryName { get; set; } //I added this field which is actually displayed on the grid
public Nullable<int> CatId { get; set; } //only used temporarily to transfer data
}
I did not end up referencing the Category model almost at all.
In my Grid I changed the binding on CategoryId to bind on CategoryName instead. Essentially with my solution, I only referenced Category Name in the view, and just matched up CategoryName with CategoryId in the Read/Update functions in the controller.
//The Title string below needs to be the same as the Name field in the EditorTemplate and possibly the same as the name in the model
columns.Bound(c => c.CategoryName).Title("CategoryName").EditorTemplateName("_CategoryDropdown");
The location of this file is important.
Views/Shared/EditorTemplates/_CategoryDropdown.cshtml:
#(
Html.Kendo().DropDownList()
.Name("CategoryName") //This name has to be the same as the Title on the main grid page
.DataTextField("CategoryName")
.DataValueField("CategoryName")
.DataSource(source =>
{
source.Read(read => { read.Action("GetCategories", "Words"); });
})
.OptionLabel("Select a category")
)
The Words/GetCategories function was correct.
I had to do some work in Words/Read to get the category name from the category ID
public ActionResult Read([DataSourceRequest] DataSourceRequest request)
{
var items = db.Words.Select(w => new WordViewModel
{
WordId = w.WordId,
CatId = w.CatId,
CategoryName = "",
WordName = w.WordName
}).ToList(); //need .ToList to be able to iterate through it
//finish building the word
foreach(var item in items)
{
if(item.CatId!=null)
{
//add CategoryName corresponding to each CatId
//In my database I have a table for Categories which matches up CatId to CategoryName
Category cat = db.Categories.Select(c => c).Where(c => c.CatId == item.CatId).FirstOrDefault();
item.CategoryName = cat.CategoryName;
}
}
return Json(items.ToDataSourceResult(request), JsonRequestBehavior.AllowGet);
}
and some stuff in Words/Update to do the reverse Name->Id:
public ActionResult Update([DataSourceRequest]DataSourceRequest request, [Bind(Prefix = "models")] WordViewModel word)
{
if (ModelState.IsValid)
{
// Create a new Product entity and set its properties from the posted ProductViewModel
var entity = new Word
{
WordId = word.WordId,
CategoryName = word.CategoryName,
WordName = word.WordName
};
if (word.CategoryName != "")
{
//match CategoryWord to CatID
Category cat = db.Categories.Select(c => c).Where(c => c.CategoryName == word.CategoryName).FirstOrDefault();
entity.CatId = cat.CatId;
}
// Attach the entity
db.Words.Attach(entity);
// Change its state to Modified so Entity Framework can update the existing product instead of creating a new one
db.Entry(entity).State = EntityState.Modified;
// Update the entity in the database
db.SaveChanges();
}
// Return the updated product. Also return any validation errors.
return Json(new [] { word }.ToDataSourceResult(request, ModelState));
}
There might be some minor errors since this is a little simplified from my real code, but all the important pieces are there. Figuring out all the linkages and what I could depend on Kendo for vs what I had to manually was pretty difficult to figure out. Good luck to anyone else trying to use Kendo Grid, and I hope this example helps!
Nullable CatId is the problem. Check out the fix here Kendo MVC dropdown lists inside inline Kendo MVC grids. Second option is following but this one only works with InLine.
function onSave(e) {
// kendo nullable dropdown bug workaround
$("#wordGrid tbody [data-role=dropdownlist]").each(function () {
var kd = $(this).data("kendoDropDownList");
if (kd) {
var v = kd.value();
var p = kd.list.attr('id').replace('-list', '');
if(p) e.model.set(p, v);
}
})
}
There are also a suggestion to use default value but it never worked for me. see here
Let's say I have two models:
public class User
{
[Key]
public int UserId { get; set; }
public string Name { get; set; }
}
and
public class Friend
{
[Key]
public int FriendId { get; set; }
public User A { get; set; }
public User B { get; set; }
}
Let's say I only have 2 users in my database (ids: 1 (Jon) and 2 (Sam)). Now I insert into table friend like this:
db.Friends.Add(new Friend()
{
A = db.Users.Find(1),
B = db.Users.Where(u => u.UserId == 2).First()
});
db.SaveChanges();
Suddenly, I find a user (3, Sam) in a table user. What is the reasoning behind this? Not completely sure if relevant or not, but note that even if I make A and B fields virtual, nothing changes.
UPDATE
Finally found how to reproduce my problem. Apparently the problem isn't exactly the same as I described.
User a, b;
using (var db = new DbConnection())
{
a = db.Users.First(u => u.UserId == 1);
b = db.Users.First(u => u.UserId == 2);
}
using (var db = new DbConnection())
{
db.Friends.Add(new Friend()
{
A = a,
B = b
});
db.SaveChanges();
}
Now users will have 4 users. Does it mean that if I step out of transaction, I can no longer access the entities as if they were exactly the same items in the current transaction? Or maybe there is a way to make the program know that I am referring to the same item (because the ID is the same)?
Honestly tried the same steps as you described and everything work well.. Anyway my steps
Created a db context class derived from `DbContext'
public class EFContext : DbContext
{
public DbSet<Friend> Friends { get; set; }
public DbSet<User> Users { get; set; }
public EFContext(string connectionString)
: base(connectionString)
{
}
}
I use MSQL2008 Express with win auth so I created the Users table
using (var db = new EFContext(#"Data Source=yourMachineName\SQLEXPRESS2008;Initial Catalog=DBName;Integrated Security=True;MultipleActiveResultSets=True"))
{
db.Users.Add(new User()
{
UserId = 1,
Name = "John"
});
db.Users.Add(new User()
{
UserId = 2,
Name = "Sam"
});
db.SaveChanges();
}
I checked my db and found 2 records
After I created the Friends table
using(var db = new EFContext(#"Data Source=yourMachineName\SQLEXPRESS2008;Initial Catalog=DBName;Integrated Security=True;MultipleActiveResultSets=True"))
{
db.Friends.Add(new Friend()
{
A = db.Users.Find(1),
B = db.Users.Where(u => u.UserId == 2).First()
});
db.SaveChanges();
}
Again I got 1 record in the Friends table with columns FriendId=1, A_UserId=1, B_UserId=2.
I checked the Users table and I still have 2 records.
If I were you I would try my code in a separate app. If it works then please post here all steps which led you to this problem.
For web application in development(ASP.Net MVC), I'm using the telerik grid. The grid is bound to an IQueryable of my list, because it's a big table, and I want that telerik apply it's filter on the list, and then executes this result, not dowloading 10'000 rows(with the joined tables), and then with the filter, use only rows.
I'm using(and I really need it for this page, it's one of the key feature) the filter/order of the grid.
One of the main column(determining the kind of the data) is an enum.
The problem is that I get a "Specified type member is not supported in linq to entities" as soon as I'm trying to filter/sort it.
I've to bind it on the enum(and not the mapped int) because if I use the id, filters/order by will be on an int, and I can't expect that the user knows the id of the foreign table.
I just cannot implement myself again all grids parameter(located in url)(I assume, it's either I do everything, or nothing) and filter it correctly, order it correctly).
Do you have an idea of workaround?
I don't know how your Entity Model looks like but I'll suppose that you've something like this Model:
public partial class Project
{
public int Id { get; set; }
public string Name { get; set; }
public int Status { get; set; }
}
and the Status property represents your enum value then you've this enum:
public enum ProjectStatuses
{
Current = 1,
Started = 2,
Stopped = 3,
Finished = 4,
}
Then just create new ViewModel like this :
public class ProjectDetails
{
public int Id { get; set; }
public string Name { get; set; }
public int Status { get; set; }
public ProjectStatuses StatusValue { get { return (ProjectStatuses) Status; } }
// This property to display in telerik ClientTemplate
public string StatusName { get { return Enum.GetName(typeof (ProjectStatuses), Status ); } }
}
And because I love Extension Methods I'll add this one :
public static class ModelListExtensions
{
public static IQueryable<ProjectDetails> ToViewModelDetails(this IQueryable<Project> modelList)
{
return modelList.Select(m => new ProjectDetails
{
Id = m.Id,
Name = m.Name,
Status = m.Status,
};
}
}
Update :
Here is the Controller
public ActionResult Index()
{
int total;
var viewModel = getGridList(out total);
ViewBag.Total = total;
return View(viewModel);
}
//this Action to get ajax pages
[GridAction(EnableCustomBinding = true)]
public ActionResult ReGetIndex(GridCommand command, int roleId)
{
int total;
var list = getGridList(out total, roleId, command);
return View(new GridModel {Data = list, Total = total});
}
private IEnumerable<ProjectDetails> getGridList(out int total, GridCommand command = null)
{
command = command ?? new GridCommand {Page = 1};
foreach (var descriptor in command.SortDescriptors)
{
if (descriptor.Member == "StatusValue")
descriptor.Member = "Status";
}
foreach (FilterDescriptor descriptor in command.FilterDescriptors)
{
if (descriptor.Member == "StatusValue")
descriptor.Member = "Status";
}
var list = modelService.AllAsQuery()
.ToViewModelDetails() // To convert it to our ViewModel if we have one
.Where(command.FilterDescriptors);
total = list.Count();
return (IEnumerable<ProjectDetails>) list.Sort(command.SortDescriptors)
.Page(command.Page - 1, command.PageSize)
.GroupBy(command.GroupDescriptors).ToIList();
}
And this is the View
#model IEnumerable<ProjectDetails>
#{
Html.Telerik()
.Grid(Model)
.Name("ProjectsGrid")
.Sortable()
.Filterable()
.EnableCustomBinding(true)
.DataBinding(dataBinding => dataBinding
.Ajax()
.Select("ReGetIndex", "Projects"))
.Pageable(page => page.Style(GridPagerStyles.PageSizeDropDown | GridPagerStyles.NextPreviousAndNumeric).Total(ViewBag.Total))
.Columns(column =>
{
column.Bound(m => m.Id).Hidden(true);
column.Bound(m => m.Name);
column.Bound(m => m.StatusValue).ClientTemplate("<#= StatusName #>");
})
.Render();
}
Update :
If you want to enforce at least one sort order you could use something like this:
if (!command.SortDescriptors.Any())
{
command.SortDescriptors.Add(new SortDescriptor {Member = "YourDefaultProperty"});
}
You don't really have choice (or few annoying choices)
Wether you use a class instead of enum (but if you used an enum, that's because it was better).
Or you "pseudo-sort" your enum, and use the mapped int.
public enum TT
{
Brown = 0,
Green = 1
}
Of course, you'll have to check the actual datas (mapped int) in your DB and update them to conform to the new order (can't change enum order without impact). And you'll have to do that everytime you want to insert a value between existing enum values.
Or you wait for next EF / linq / c# version, which should have enum support in linq2entities