Setting data in multiple nested lists ASP.NET MVC C# - c#

I'm an intern with very basic knowledge of ASP and C#.
I'm trying to display a list of projects > maps > thememaps in an application I'm working on in ASP.NET MVC. In my third foreach I get an error saying: "Does not contain a definition for "ThemeMaps" and no extension method "ThemeMaps" accepting a first argument of type could be found".
I'm confused as to why vmProject.Maps does not contain the property ThemeMaps. I instantiated that list just like maps. What am I doing wrong?
LayersController.cs
// Create viewmodel object
var viewModel = new AddLayerToThemeMapViewModel();
// Create Project list
viewModel.Projects = new List<AddProject>();
// Loop over all maps
List<Project> projects = this.applicationDb.Projects.OrderBy(e => e.Title).ToList();
foreach (var project in projects)
{
// Create map
var vmProject = new AddProject()
{
ProjectId = project.ProjectID,
ProjectTitle = project.Title,
Maps = new List<AddLayerMap>(),
};
foreach (var map in project.Maps.OrderBy(e => e.Title))
{
// Create map
vmProject.Maps.Add(new AddLayerMap()
{
MapId = map.MapId,
MapTitle = map.Title,
ThemeMaps = new List<AddLayerMapThemeMap>(),
});
// Loop over all thememaps in map
foreach (var thememap in map.ThemeMaps.OrderBy(e => e.Order))
{
vmProject.Maps.ThemeMaps.Add(new AddLayerMapThemeMap()
{
ThemeMapId = thememap.ThemeMapId,
ThemeMapTitle = thememap.Title,
});
}
}
// Add map to list
viewModel.Projects.Add(vmProject);
}
My viewmodel class
using Mapgear.MapViewer.Entities;
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace Mapgear.MapViewer.ViewModels
{
public class AddLayerToThemeMapViewModel
{
public Guid LayerId { get; set; }
public List<AddProject> Projects { get; set; }
}
public class AddProject
{
public Guid ProjectId { get; set; }
public string ProjectTitle { get; set; }
public List<AddLayerMap> Maps { get; set; }
}
public class AddLayerMap
{
public Guid MapId { get; set; }
public string MapTitle { get; set; }
public List<AddLayerMapThemeMap> ThemeMaps { get; set; }
}
public class AddLayerMapThemeMap
{
public Guid ThemeMapId { get; set; }
public string ThemeMapTitle { get; set; }
}
}
I made a scetch before I started on paper, which looks like the following:
LayerId
List project
ProjectId
ProjectTitle
List Map
MapId
MapTitle
List ThemeMap
ThemeMapId
ThemeMapTitle
I know my class names are a bit out of wack, however I din't write them myself. Gonna optimize them after.
PS: This is my first question on StackOverflow!

I modified the code in the second for-each loop a bit. I added a variable for the newly created map and add the thememaps to this created map. Check if it's correct.
var newMap = new AddLayerMap()
{
MapId = map.MapId,
MapTitle = map.Title,
ThemeMaps = new List<AddLayerMapThemeMap>(),
};
// Create map
vmProject.Maps.Add(newMap);
// Loop over all thememaps in map
foreach (var thememap in map.ThemeMaps.OrderBy(e => e.Order))
{
newMap.ThemeMaps.Add(new AddLayerMapThemeMap()
{
ThemeMapId = thememap.ThemeMapId,
ThemeMapTitle = thememap.Title,
});
}

Related

Map base class to specific implementation for collections

Is it possible to map from a source-object to a specific implementation of a target object using automapper?
In the following code-sample, I want to map from SourceItem to TargetSampleItem and not to TargetBaseItem. Important: TargetBaseItem can't be abstract. But if I use the following mapper-configuration, always TargetBaseItem is used.
To summarize things up, the following collection should contain items of type TargetSampleItem, which is derriving from TargetBaseItem:
public IList<TargetItemBase> Items { get; set; } = new List<TargetItemBase>();
Complete code
using AutoMapper;
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MapperTest
{
class Program
{
static void Main(string[] args)
{
var configuration = new MapperConfiguration(cfg =>
{
cfg.CreateMap<SourceRoot, TargetRoot>();
cfg.CreateMap<SourceItem, TargetItemBase>()
.IncludeAllDerived();
});
configuration.AssertConfigurationIsValid();
var mapper = configuration.CreateMapper();
var source = new SourceRoot
{
Id = 1,
Items = new List<SourceItem>
{
new SourceItem { A = "a1", B = "b1" },
new SourceItem { A = "a2", B = "b2" }
}
};
var result = mapper.Map<TargetRoot>(source);
// Should retur true
Console.WriteLine(result.Items.First() is TargetSampleItem);
Console.ReadLine();
}
/// <summary>
/// Source model
/// </summary>
public class SourceRoot
{
public int Id { get; set; }
public IList<SourceItem> Items { get; set; } = new List<SourceItem>();
}
public class SourceItem
{
public string A { get; set; }
public string B { get; set; }
}
/// <summary>
/// Target model
/// </summary>
public class TargetRoot
{
public int Id { get; set; }
public IList<TargetItemBase> Items { get; set; } = new List<TargetItemBase>();
}
public class TargetItemBase
{
public string A { get; set; }
}
public class TargetSampleItem : TargetItemBase
{
public string B { get; set; }
}
}
}
EDIT
using As<> is not working, because than AutoMapper is not mapping to the type, rather than just casting it:
cfg.CreateMap<SourceItem, TargetItemBase>()
.As<TargetSampleItem>();
EDIT 2/Solution
Using As<> is working, if a map between SourceItem and TargetSampleItem is added too:
cfg.CreateMap<SourceItem, TargetItemBase>().As<TargetSampleItem>();
cfg.CreateMap<SourceItem, TargetSampleItem>();
As does work for me:
cfg.CreateMap<SourceItem, TargetItemBase>().As<TargetSampleItem>();
cfg.CreateMap<SourceItem, TargetSampleItem>();
If As<> doesn't work for you, then a possible solution might be using AfterMap like -
CreateMap<SourceRoot, TargetRoot>()
.AfterMap((s, d) =>
{
s.Items.ToList().ForEach(p => d.TargetItems.Add(new TargetSampleItem { A = p.A, B = p.B }));
});
(Its not an elegant solution, but since TargetSampleItem is not the target of any of your maps, I don't see any reason why AutoMapper would create an instance of it).
You have to rename either of the Items properties so that AutoMapper doesn't try to map them automatically (I renamed the one in TargetRoot class to TargetItems). And of course you don't need the mapping between SourceItem and TargetItemBase.

Raw queries with overridden column names

I'm trying to retrieve some entities using Entity Framework by querying an XML column. Entity Framework doesn't support this so I had to use raw SQL.
var people = context.People.SqlQuery("SELECT * FROM [People] WHERE [DataXML].value('Properties/Age', 'int') = 21").AsQueryable().AsNoTracking();
My person class:
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
[Column("YearsSinceBirth")]
public int Age { get; set; }
[Column(TypeName = "xml")]
public string DataXML { get; set; }
}
This should work, however, it falls over when trying to map it back to an object. Specifically, it's falling over on the Age property, which has it's column name overridden to "YearsSinceBirth".
'The data reader is incompatible with the specified
'MyProject.CodeBase.DataModel.DbEntities.Person'. A member of the
type, 'Age', does not have a corresponding column in the data reader
with the same name.'
I'm guessing that Entity Framework doesn't map database column names to object property names and therefore is expecting the column to be named 'Age' rather than 'YearsSinceBirth'.
I don't want to have to list each column and their mapping in the SQL query (like SELECT YearsSinceBirth As Age) as the actual project I'm working on which has this column has a lot more columns and that would mean this query would break every time the schema changed (kinda defeating the purpose of Entity Framework).
If this is EF Core, your problem is not that SqlQuery() doesn't support mapping column names (it does). Rather your problem is that your table doesn't contain a column called YearsSinceBirth, and you are returning 'select *'.
If you have a column called YearsSinceBirth, this works fine. Although you will be retrieving the value in the YearsSinceBirth column, not the value in the XML document. EG
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
//using Microsoft.Samples.EFLogging;
using System.ComponentModel.DataAnnotations.Schema;
using System.ComponentModel.DataAnnotations;
using System.Data.SqlClient;
namespace EFCore2Test
{
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
[Column("YearsSinceBirth")]
public int Age { get; set; }
[Column(TypeName = "xml")]
public string DataXML { get; set; }
}
public class Location
{
public string LocationId { get; set; }
}
public class Db : DbContext
{
public DbSet<Person> People { get; set; }
public DbSet<Location> Locations { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer("Server=(local);Database=EFCoreTest;Trusted_Connection=True;MultipleActiveResultSets=true");
base.OnConfiguring(optionsBuilder);
}
}
class Program
{
static void Main(string[] args)
{
using (var db = new Db())
{
db.Database.EnsureDeleted();
//db.ConfigureLogging(s => Console.WriteLine(s));
db.Database.EnsureCreated();
var p = new Person()
{
Name = "joe",
Age = 2,
DataXML = "<Properties><Age>21</Age></Properties>"
};
db.People.Add(p);
db.SaveChanges();
}
using (var db = new Db())
{
var people = db.People.FromSql("SELECT * FROM [People] WHERE [DataXML].value('(/Properties/Age)[1]', 'int') = 21").AsNoTracking().ToList() ;
Console.WriteLine(people.First().Age);
Console.ReadLine();
}
Console.WriteLine("Hit any key to exit");
Console.ReadKey();
}
}
}
You can use a pattern similar to this to project entity attributes from an XML or JSON column:
public class Person
{
private XDocument xml;
public int Id { get; set; }
public string Name { get; set; }
[NotMapped]
public int Age
{
get
{
return int.Parse(xml.Element("Properties").Element("Age").Value);
}
set
{
xml.Element("Properties").Element("Age").Value = value.ToString();
}
}
[Column(TypeName = "xml")]
public string DataXML
{
get
{
return xml.ToString();
}
set
{
xml = XDocument.Parse(value);
}
}
}
You can dynamically create select query with aliases, if they needed, with the help of reflection and ColumnAttribute checking:
public string SelectQuery<T>() where T : class
{
var selectQuery = new List<string>();
foreach (var prop in typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
var attr = prop.GetAttribute<ColumnAttribute>();
selectQuery.Add(attr != null ? $"{attr.Name} as {prop.Name}" : prop.Name);
}
return string.Join(", ", selectQuery);
}
Usage:
var people = context.People.SqlQuery($"SELECT {SelectQuery<Person>()} FROM [People] WHERE [DataXML].value('Properties/Age', 'int') = 21")
.AsQueryable().AsNoTracking();

Entity Framework 5 - LINQ syntax error

I have the following code where I am creating a IList that I need to filter by the data in another list called List. The locations list represents the locations a user is allowed to view based on their permissions. I am new to LINQ and am confused with error I get (C# Unknown method "Where(?)" of "System.Ling.IQueryable". I have tried various syntax arrangement using either Contains() and Any() or both to no avail. I feel like it's something very basic that I don't understand about doing this. Here is the code:
----- users locations
using System;
using System.ComponentModel.DataAnnotations;
using System.Linq;
namespace Decking.Models
{
public class locations
{
[Key]
public string org_id { get; set; }
}
}
///////// here is the view model
using System.ComponentModel.DataAnnotations;
using System;
namespace Decking.Models
{
public class InventoryViewModel
{
[Key]
public int id { get; set; }
public DateTime metric_dt { get; set; }
public int? item_id { get; set; }
public int? loc_type_id { get; set; }
public string trlr_nbr { get; set; }
public string user_id { get; set; }
public string org_id { get; set; }
public Double numerator { get; set; }
//these are the child entities
[UIHint("ClientItem")]
public ItemViewModel Items
{
get;
set;
}
[UIHint("ClientLocTypes")]
public LocTypesViewModel LocTypes
{
get;
set;
}
[UIHint("ClientOrgsByUser")]
public OrgsByUserViewModel OrgsByUser
{
get;
set;
}
}
}
///////// code to populate the view model
public IList<InventoryViewModel> GetAll(List<locations> locs)
{
IList<InventoryViewModel> result = new List<InventoryViewModel>();
result = entities.inventory.Select(inventory => new
InventoryViewModel
{
id = inventory.id,
metric_dt = inventory.metric_dt,
item_id = inventory.item_id,
loc_type_id = inventory.loc_type_id,
trlr_nbr = inventory.trlr_nbr,
org_id = inventory.org_id,
numerator = inventory.numerator,
user_id = inventory.user_id,
Items = new ItemViewModel()
{
item_id = inventory.items.item_id,
item_desc = inventory.items.item_desc,
},
LocTypes = new LocTypesViewModel()
{
loc_type_id = inventory.loc_types.loc_type_id,
loc_desc = inventory.loc_types.loc_desc,
},
OrgsByUser = new OrgsByUserViewModel()
{
user_id = inventory.user_id,
//mgr_emp_nbr = inventory.mgr,
org_id = inventory.org_id,
},
}).Where(e => e.metric_dt == DateTime.Today && e.org_id
==locs.Any(o=>o.org_id)) // this doesn't work
//}).Where(e => e.metric_dt == DateTime.Today && e.org_id == "SGF") //
this works
.ToList();
return result;
}
Any help you can provide would be greatly appreciated! Thank so much!
The problem is in e.org_id == locs.Any(o=>o.org_id). As I can see in your working example, your org_id is a string.
I guess what you are trying to do is .Where(e => e.metric_dt == DateTime.Today && locs.Any(o=>o.org_id == e.org_id))

Based on class that has no key defined mvc5

I try to add view from this controller . I only need this view to show data not for insert or update or delete
public ActionResult Index()
{
var CartObj = ShoppingCart.GetCart(this.HttpContext);
var classshop = new New
{
CartItems = CartObj.GetCartItems(),
CartTotal = CartObj.GetSum()
};
return View(classshop);
}
namespace MusicStore.Models
{
public class ShoppingCart
{
MusicStoreEntities dbo = new MusicStoreEntities();
string ShoppingCartID { get; set; }
public const string CartSessionKey = "CartId";
public static ShoppingCart GetCart(HttpContextBase Context)
{
var cart = new ShoppingCart();
cart.ShoppingCartID = cart.GetCardId(Context);
return cart;
}
public static ShoppingCart GetCart(Controller controller)
{
return GetCart(controller.HttpContext);
}
public List<Cart> GetCartItems()
{
return dbo.Carts.Where(a => a.CartId == ShoppingCartID).ToList();
}
public decimal? GetSum()
{
decimal? Sum = (from items in dbo.Carts
where items.CartId == ShoppingCartID
select (int)items.Count * items.album.Price).Sum();
return Sum ?? decimal.Zero;
}
}
}
and then I got this error:
there was an error running the selected code generator:
'unable to retrieve metadata for 'Musicstore.Model.new'
one or more validation error were detected during model generation
musicstore,models.New :entity type'New' has no key defined .
define the key of entityType
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace MusicStore.Models
{
public class New
{
public List<Cart> CartItems { get; set; }
public decimal? CartTotal { get; set; }
}
}
There are two options here. First, if this class is mapped to a table in your database, every model in entity framework requires a primary key. Add this into your model:
[Key]
public int Id { get; set; }
This creates a new property called Id and the [Key] attribute makes it a primary key. Technically you don't need the attribute as EF will pick up Id property and use it as a key, but I prefer to be explicit.
Alternatively, if you don't want this class to be a table in your database, add the NotMapped attribute to the class like this:
[NotMapped]
public class New
{
public List<Cart> CartItems { get; set; }
public decimal? CartTotal { get; set; }
}
I know this is old, but I just ran across this issue.
What happen is when I created a class, CreateEmployeeViewModel, inside the Models folder Visual Studio "smartly" put a line in my DB Context class
public System.Data.Entity.DbSet<eManager.Web.Models.CreateEmployeeViewModel>
CreateEmployeeViewModels { get; set; }
So a table was created on the next update-migration. Removing this line removed the requirement for a key field.
Note: You may also have to add the line AutomaticMigrationDataLossAllowed = true; to your DBMigrationConfiguration Class if the table was created.

Entity Framework 5 Won't Fetch Relationships With Include()

I am quite certain that questions like this have been answered a number of times before, but I can't get any of the suggestions to work.
I am building a MVC 4 application with Entity Framework 5, where the entities were generated from existing tables. I have entity classes that look like this:
namespace RebuildingModel
{
using System;
using System.Collections.Generic;
public partial class StandardCodeTable
{
public StandardCodeTable()
{
this.StandardCodeTableTexts = new HashSet<StandardCodeTableText>();
}
public int TableCode { get; set; }
public string RefTableName { get; set; }
public virtual ICollection<StandardCodeTableText> StandardCodeTableTexts { get; set; }
}
}
namespace RebuildingModel
{
using System;
using System.Collections.Generic;
public partial class StandardCodeTableText
{
public int TableCode { get; set; }
public string LanguageCode { get; set; }
public string TextVal { get; set; }
public virtual StandardCodeTable StandardCodeTable { get; set; }
}
}
namespace RebuildingSite.Models
{
public class CodeTableJoined
{
public int TableCode { get; set; }
public string ReferenceTableName { get; set; }
public string LanguageCode { get; set; }
public string TextValue { get; set; }
}
}
I have a DAO that looks like this:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace RebuildingModel.Dao
{
public class CodeTableDao
{
public CodeTableDao() { }
public ISet<StandardCodeTableText> GetCode(string refTableName)
{
HashSet<StandardCodeTableText> codes = new HashSet<StandardCodeTableText>();
using (var db = new RebuildingTogetherEntities())
{
db.StandardCodeTableTexts.Include("StandardCodeTables");
var query = from c in db.StandardCodeTableTexts
where c.StandardCodeTable.RefTableName == refTableName
orderby c.TableCode
select c;
foreach (var item in query)
{
codes.Add(item);
}
}
return codes;
}
}
I have a controller that looks like this:
namespace RebuildingSite.Controllers
{
public class CodeTableController : Controller
{
public ActionResult Index(string refTableName)
{
CodeTableDao dao = new CodeTableDao();
ICollection<StandardCodeTableText> codes = dao.GetCode(refTableName);
HashSet<CodeTableJoined> joins = new HashSet<CodeTableJoined>();
foreach (var code in codes)
{
CodeTableJoined join = new CodeTableJoined();
join.TableCode = code.TableCode;
join.LanguageCode = code.LanguageCode;
join.TextValue = code.TextVal;
join.ReferenceTableName = code.StandardCodeTable.RefTableName;
joins.Add(join);
}
ISet<string> refTableNames = dao.GetReferenceTables();
ViewBag.RefTableNames = refTableNames;
return View(joins);
}
}
}
When I run the view attached to the controller, an ObjectDisposedException is thrown at this line, where the relationship is used:
join.ReferenceTableName = code.StandardCodeTable.RefTableName;
This has to be something simple. What am I doing wrong? I have tried adding that Include() call in from the context in many different places, even multiple times.
I've also tried adding an explicit join in the Linq query. I can't get EF to fetch that relationship.
Copying my comment to an answer - Put the include be in the actual query
var query = from c in
db.StandardCodeTableTexts.include("StandardCodeTables"). where
c.StandardCodeTable.RefTableName == refTableName orderby c.TableCode
select c;

Categories