TryGetObjectByKey() doesn't return entity with Added state (EF 6) - entity-framework

1. Q #1
I have POCO
public class Product
{
public string Id { get; set; }
public string Name { get; set; }
public ICollection<Version> Versions { get; set; }
}
In my DbContext I have func
public void AttachUpdated<T>( T objectDetached) where T : class
{
var objContext = ((IObjectContextAdapter)this).ObjectContext;
var objSet = objContext.CreateObjectSet<T>();
var entityKey = objContext.CreateEntityKey(objSet.EntitySet.Name, objectDetached);
object original;
if (objContext.TryGetObjectByKey(entityKey, out original))
objContext.ApplyCurrentValues(entityKey.EntitySetName, objectDetached);
else
objContext.AddObject(entityKey.EntitySetName, objectDetached);}
So i want to add some Products to context
var p1 = new Product(){Id = "1", Name = "Product 1";}
var p2 = new Product(){Id = "1", Name = "Product 1";}
ctx.AttachUpdated(p1);
And when i try to add identical Product (with same Id as first product) TryGetObjectByKey() doesn't find already added product.
ctx.AttachUpdated(p2);
Therefore I need to use ctx.SaveChanges() or AccseptAllChanges() and then
ctx.AttachUpdated(p2) work as expected.
I can't understand where i have problem in my code.
Q #2
var p1 = new Product() { Id = "1", Name = "Product 1" };
var v1 = new Version() { Number = "1.0", Type = "Release", ReleaseDate = "01/01/13" };
p1.Versions = new List<Version>();
p1.Versions.Add(v1);
ctx.AttachUpdated(p1);
And then i see that v1 was addet to DbSet(). But why? And how i could prevent such bihavior. I need to add only Product and not related Versions.

public void AttachOrUpdate<T>(T entity) where T : class
{
var objContext = ((IObjectContextAdapter)context).ObjectContext;
var objSet = objContext.CreateObjectSet<T>();
var entityKey = objContext.CreateEntityKey(objSet.EntitySet.Name, entity);
var original = this.context.Set<T>().Find(entityKey.EntityKeyValues[0].Value);
if (original != null)
{
this.context.Entry<T>(original).CurrentValues.SetValues(entity);
}
else
objContext.AddObject(entityKey.EntitySetName, entity);
}

Related

Entity Framework Migrations seeds duplicate rows

I've got two classes
public class Category
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Item
{
public int Id { get; set; }
public sting Name { get; set; }
public Category Category { get; set; }
}
I have EF Migrations and the following seed:
var instockCategory = new Category() { Name = "InStock" };
var outofStockCategory = new Category() { Name = "OutOfStock" };
context.Items.AddOrUpdate(
d => d.Name,
new Item() { Name = "Item1", Category = instockCategory },
new Item() { Name = "Item2", Category = outofStockCategory },
new Item() { Name = "Item3", Category = outofStockCategory }
);
The line "d => d.Name" makes sure that based on the name of the item, there won't be duplicate records when I reseed the database.
However, the first time I execute this, two categories are created with id 1 and 2. But the second time I run this, 3 new categories are created!
Can I fix this without manually adding every single category first?
You have to use AddOrUpdate for your categories too.
var instockCategory = default(Category);
var outofStockCategory = default(Category);
context.Set<Category>().AddOrUpdate(
c => c.Name,
instockCategory = new Category() { Name = "InStock" },
outofStockCategory = new Category() { Name = "OutOfStock" }
);
context.Items.AddOrUpdate(
d => d.Name,
new Item() { Name = "Item1", Category = instockCategory },
new Item() { Name = "Item2", Category = outofStockCategory },
new Item() { Name = "Item3", Category = outofStockCategory }
);
An explicit DbSet on your Context class is not necessary.
public class Context : DbContext
{
public DbSet<Item> Items { get; set; }
}

Why doesn't entity framework return any rows from related tables

I wrote this code
class Student {
public Student() {
this.Courses = new HashSet<Course>();
}
public int ID { get; set; }
public string Name { get; set; }
public virtual ICollection<Course> Courses { get; set; }
}
class Course {
public Course() {
this.Students = new HashSet<Student>();
}
public int ID { get; set; }
public string Name { get; set; }
public virtual ICollection<Student> Students { get; set; }
}
class SchoolDBContext : DbContext {
public DbSet<Student> Students { get; set; }
public DbSet<Course> Courses { get; set; }
public SchoolDBContext()
: base("SchoolDbConnectionString") {
}
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
}
}
My Seed method looks like
protected override void Seed(ConsoleApplication6.SchoolDBContext context)
{
Course c1 = new Course { ID = 1, Name = "Chemistry" };
Course c2 = new Course { ID = 2, Name = "Maths" };
Course[] courses = new Course[2];
courses[0] = c1;
courses[1] = c2;
Student s1 = new Student { ID = 1, Name = "Student 1" };
Student s2 = new Student { ID = 1, Name = "Student 2" };
Student[] students = new Student[2];
students[0] = s1;
students[1] = s2;
c1.Students = students;
c2.Students = students;
context.Courses.AddOrUpdate(course => new { course.ID }, courses);
}
After I run Update-database I can see that the database has 3 tables. Student and Course tables have 2 rows each and StudentCourse table has 4 rows. So I guess all data is seeded correctly.
Now when I write this code in my main method
static void Main(string[] args) {
SchoolDBContext c = new SchoolDBContext();
c.Configuration.LazyLoadingEnabled = true;
Student s = (from student in c.Students where student.ID == 1 select student).FirstOrDefault();
List<Course> courses = s.Courses.ToList();
Console.WriteLine(s.Name);
Console.WriteLine(courses.Count);
foreach (Course co in courses) {
Console.WriteLine(co.Name);
}
}
it prints the name of the student correctly... but prints 0 for courses.Count and the forloop on courses List returns nothing.
why am I not able to get the courses for student 1?
I also tried the other way round
static void Main(string[] args) {
SchoolDBContext c = new SchoolDBContext();
c.Configuration.LazyLoadingEnabled = true;
Course co = (from course in c.Courses where course.ID == 1 select course).FirstOrDefault();
Console.WriteLine(co.Name);
List<Student> students = co.Students.ToList();
foreach (Student s in students) {
Console.WriteLine(s.Name);
}
}
here also the name of the course is returned correctly... but it doesn't print any of the students.
So entity framework is not able to walk to the related table and fetch rows from there.
What's going on?
Found the answer myself.
static void Main(string[] args) {
SchoolDBContext c = new SchoolDBContext();
c.Configuration.LazyLoadingEnabled = false;
Course co = (from course in c.Courses.Include("Students") where course.ID == 1 select course).FirstOrDefault();
Console.WriteLine(co.Name);
List<Student> students = co.Students.ToList();
foreach (Student s in students) {
Console.WriteLine(s.Name);
}
}
putting it here so that it benefits someone....
But according to me my original code should have worked (lazy loading) so I don't understand why my original code which was doing lazy loading did not work.
OK. here is the solution with lazy loading
static void Main(string[] args) {
SchoolDBContext context = new SchoolDBContext();
context.Configuration.LazyLoadingEnabled = true;
Course co = (from course in context.Courses where course.ID == 1 select course).FirstOrDefault();
//Course co = (from course in c.Courses where course.ID == 1 select course).FirstOrDefault();
Console.WriteLine(co.Name);
foreach (Student s in context.Entry(co).Collection(c => c.Students).Query()) {
Console.WriteLine(s.Name);
}
}
This was really useful
http://msdn.microsoft.com/en-us/data/jj574232#lazy

Automapper and mapping Parent / Child relationships causes a StackOverflow error when Entity Framework generate DynamicProxies?

I have tried many options to get Automapper to correctly map Parent / Child relationships.
Model:
//Entity
public class WorkArea
{
public Guid Id;
public Name {get;set;}
public Guid? ParentWorkAreaId {get;set;} //for entity Framework Foreign Key
public WorkArea ParentWorkArea {get;set;}
public ICollection<WorkArea> ChildWorkareas {get;set;}
}
//DTO
public class WorkAreaDto
{
public Guid Id;
public Name {get;set;}
public Guid? ParentWorkAreaId {get;set;} //for entity Framework Foreign Key
public WorkAreaDto ParentWorkArea {get;set;}
public ICollection<WorkAreaDto> ChildWorkareas {get;set;}
}
This mapping causes a Stack Overflow:
Mapper.CreateMap<WorkArea,WorkAreaDto>();
I tried something exactly like this and had the same error
I then created a custom TypeConverter, but not only do I have to write recursive methods for children, but also parents. Just seems like a lot of work do get this to map correctly. Not sure if I am doing something wrong. I am using 2.0
Update:
I think my issue is the System.Data.Entity.DynamicProxies generated by Entity Framework.
This works perfectly well for me, perhaps you should inspect your data:
[TestMethod]
public void TestMethod1()
{
Mapper.CreateMap<WorkArea, WorkAreaDto>();
var source = CreateSource();
WorkAreaDto destination = new WorkAreaDto();
Mapper.Map(source, destination);
Assert.AreEqual(destination.ChildWorkareas.Count, 3);
}
private WorkArea CreateSource()
{
var id = Guid.NewGuid();
var result = new WorkArea();
result.Id = id;
result.Name = "Name" + id.ToString();
result.ParentWorkArea = CreateSourceParent(result);
result.ParentWorkAreaId = result.ParentWorkArea.Id;
result.ChildWorkareas = CreateSourceChildren(result);
return result;
}
private ICollection<WorkArea> CreateSourceChildren(WorkArea parent)
{
var result = new Collection<WorkArea>
{
new WorkArea() { Id = Guid.NewGuid(), Name = "Child1", ParentWorkArea = parent, ParentWorkAreaId = parent.Id },
new WorkArea() { Id = Guid.NewGuid(), Name = "Child2", ParentWorkArea = parent, ParentWorkAreaId = parent.Id },
new WorkArea() { Id = Guid.NewGuid(), Name = "Child3", ParentWorkArea = parent, ParentWorkAreaId = parent.Id }
};
return result;
}
private WorkArea CreateSourceParent(WorkArea source)
{
var id = Guid.NewGuid();
var result = new WorkArea();
result.Id = id;
result.Name = "Name" + id.ToString();
result.ChildWorkareas = new Collection<WorkArea>
{
source
};
return result;
}

creating list of custom object in mvc2 controller

Model::::
public class Model1
{
public string Name { get; set; }
public string ProductName { get; set; }
}
ViewModel::::
public class ViewModel1
{
public List<Model1> model1;
}
controller:::::::::
var sent = entities.Table1.Where<Table1>(o => o.SenderUserId == userId );
ViewModel1 newViewModel = new ViewModel1();
foreach (Table1 gf in sent)
{
var nmodel = new Model1();
nmodel.Name = gf.Name;
nmodel.ProductName = doSomething(gf.ProductName);
// **Here I'm stuck====how do I add nmodel to newViewModel**
//**newViewModel.Add===does not work**
}
return View(newViewModel);
A quick guess based on the code you posted, is that you never instantiated the collection.
public class ViewModel1
{
List<Model1> model1;
public ViewModel1()
{
model1=new List<Model1>();
}
}
......
newViewModel.model1.Add(nmodel);
Change your ViewModel as follows
ViewModel::::
public class ViewModel1
{
public List<Model1> model1 = new List<Model1>();
}
Change your controller as follows:
var sent = entities.Table1.Where<Table1>(o => o.SenderUserId == userId );
ViewModel1 newViewModel = new ViewModel1();
foreach (Table1 gf in sent)
{
var nmodel = new Model1();
nmodel.Name = gf.Name;
nmodel.ProductName = doSomething(gf.ProductName);
newViewModel.model1.Add(nmodel);
}
return View(newViewModel);

Optgroup drop-down support in MVC - Problems with Model Binding

I wonder if anyone can shed some light on this problem..
I've got an option group drop-down for selecting a person's ethnicity – however it’s not storing the value in the model.
ViewModel
[UIHint("EthnicOriginEditorTemplate")]
[DisplayName("Question 6: Ethnic Origin")]
public int EthnicOrigin { get; set; }
Helper : GroupDropList.Cs
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Mvc;
using System.Web.Routing;
namespace Public.Helpers
{
public static class GroupDropListExtensions
{
public static string GroupDropList(this HtmlHelper helper, string name, IEnumerable<GroupDropListItem> data, int SelectedValue, object htmlAttributes)
{
if (data == null && helper.ViewData != null)
data = helper.ViewData.Eval(name) as IEnumerable<GroupDropListItem>;
if (data == null) return string.Empty;
var select = new TagBuilder("select");
if (htmlAttributes != null)
select.MergeAttributes(new RouteValueDictionary(htmlAttributes));
select.GenerateId(name);
var optgroupHtml = new StringBuilder();
var groups = data.ToList();
foreach (var group in data)
{
var groupTag = new TagBuilder("optgroup");
groupTag.Attributes.Add("label", helper.Encode(group.Name));
var optHtml = new StringBuilder();
foreach (var item in group.Items)
{
var option = new TagBuilder("option");
option.Attributes.Add("value", helper.Encode(item.Value));
if (SelectedValue != 0 && item.Value == SelectedValue)
option.Attributes.Add("selected", "selected");
option.InnerHtml = helper.Encode(item.Text);
optHtml.AppendLine(option.ToString(TagRenderMode.Normal));
}
groupTag.InnerHtml = optHtml.ToString();
optgroupHtml.AppendLine(groupTag.ToString(TagRenderMode.Normal));
}
select.InnerHtml = optgroupHtml.ToString();
return select.ToString(TagRenderMode.Normal);
}
}
public class GroupDropListItem
{
public string Name { get; set; }
public List<OptionItem> Items { get; set; }
}
public class OptionItem
{
public string Text { get; set; }
public int Value { get; set; }
}
}
This is my EditorTemplate
<%# Import Namespace="Public.Helpers"%>
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<int>"%>
<%=Html.GroupDropList("EthnicOrigin",
new[]
{
new GroupDropListItem
{
Name = "Ethnicity",
Items = new List<OptionItem>
{
new OptionItem {Value = 0, Text = "Please Select"}
}
},
new GroupDropListItem
{
Name = "a) White",
Items = new List<OptionItem>
{
new OptionItem {Value = 1, Text = "British"},
new OptionItem {Value = 2, Text = "Irish"},
new OptionItem {Value = 3, Text = "Other White (Please specify below)"}
}
},
--snip
}, Model, null)%>
And in the view I'm referencing it as:
<%=Html.EditorFor(x => x.EthnicOrigin, "EthnicOriginEditorTemplate")%>
However it's not passing through the selected Value into the model... has anyone experienced similar problems... many thanks in advance for some pointers.
Your select doesn't have a name attribute and so when you submit the form the selected value is not sent to the server. You need to add a name:
select.GenerateId(name);
select.MergeAttribute("name", name);
Just changed the helper class to get it work for MVC 3 and with nullable int.
Thanks a lot for the class, saves me plenty of time.
public static class GroupDropListExtensions
{
public static MvcHtmlString GroupDropList(this HtmlHelper helper, string name, IEnumerable<GroupDropListItem> data, int? SelectedValue, object htmlAttributes)
{
if (data == null && helper.ViewData != null)
data = helper.ViewData.Eval(name) as IEnumerable<GroupDropListItem>;
if (data == null) return new MvcHtmlString(string.Empty);
var select = new TagBuilder("select");
if (htmlAttributes != null)
select.MergeAttributes(new RouteValueDictionary(htmlAttributes));
select.GenerateId(name);
select.MergeAttribute("name", name);
var optgroupHtml = new StringBuilder();
var groups = data.ToList();
foreach (var group in data)
{
var groupTag = new TagBuilder("optgroup");
groupTag.Attributes.Add("label", helper.Encode(group.Name));
var optHtml = new StringBuilder();
foreach (var item in group.Items)
{
var option = new TagBuilder("option");
option.Attributes.Add("value", helper.Encode(item.Value));
if (SelectedValue != 0 && item.Value == SelectedValue)
option.Attributes.Add("selected", "selected");
option.InnerHtml = helper.Encode(item.Text);
optHtml.AppendLine(option.ToString(TagRenderMode.Normal));
}
groupTag.InnerHtml = optHtml.ToString();
optgroupHtml.AppendLine(groupTag.ToString(TagRenderMode.Normal));
}
select.InnerHtml = optgroupHtml.ToString();
return new MvcHtmlString(select.ToString(TagRenderMode.Normal));
}
}
public class GroupDropListItem
{
public string Name { get; set; }
public List<OptionItem> Items { get; set; }
}
public class OptionItem
{
public string Text { get; set; }
public int Value { get; set; }
}
This is supported natively using SelectListGroup as of ASP.NET MVC 5.2:
var items = new List<SelectListItem>();
var group1 = new SelectListGroup() { Name = "Group 1" };
items.Add(new SelectListItem() { Text = "Item1", Group = group1 });
Then in MVC, do
#Html.DropDownList("select", items)