I am trying to grasp EF Code First but I still do not get how to access the referenced objects from another class (due to lack of enough knowledge, I cannot even formulate the question).
Here's what my simple code looks like:
public class Destination
{
public int DestinationId { get; set; }
public string Name { get; set; }
public string Country { get; set; }
public string Description { get; set; }
public byte[] Photo { get; set; }
public List<Lodging> Lodgings { get; set; }
}
public class Lodging
{
public int LodgingId { get; set; }
public string Name { get; set; }
public string Owner { get; set; }
public bool IsResort { get; set; }
public Destination Destination { get; set; }
}
public class BreakAwayContext: DbContext
{
public DbSet<Destination> Destinations { get; set; }
public DbSet<Lodging> Lodgings { get; set; }
}
private static void InsertDestination()
{
var destination = new Destination
{
Country = "Indonesia",
Description = "EcoTourism at its best in exquisite Bali",
Name = "Bali"
};
using(var context = new BreakAwayContext())
{
context.Destinations.Add(destination);
context.SaveChanges();
}
}
private static void InsertLodging()
{
var lodging = new Lodging()
{
Name = "x",
IsResort = false,
Owner = "asdasd"
};
using(var context = new BreakAwayContext())
{
var dest = context.Destinations.Find(1);
lodging.Destination = dest;
context.Lodgings.Add(lodging);
context.SaveChanges();
}
}
private static void ShowLodgings()
{
using(var context = new BreakAwayContext())
{
foreach(var l in context.Lodgings)
{
Console.WriteLine("{0} {1} {2}", l.Name, l.Owner, l.Destination.Name);
}
}
}
I get a NullReferenceException on the line I try to write the destination name to the console.
Thanks in advance.
Try using navigation properties:
First make Destination virtual
public virtual Destination Destination { get; set; }
Then use Include method
foreach(var l in context.Lodgings.Include(x => x.Destination))
Just set Destination property in your Lodging class, virtual. This tell, the EF to load Destination automatically when you need it(Lazy Loading).
So your Lodging class should looks like:
public class Lodging
{
public int LodgingId { get; set; }
public string Name { get; set; }
public string Owner { get; set; }
public bool IsResort { get; set; }
public virtual Destination Destination { get; set; }
}
Related
As I said in the title, I'm trying to convert in the get method a model object to its DTO.
My method is to get users and is the next piece of code:
// GET: api/Users
[HttpGet]
public async Task<ActionResult<IEnumerable<UserDTO>>> GetUsers()
{
var users = _context.Users.ToList();
var userDtos = new List<UserDTO>();
foreach (var user in users)
{
userDtos.Add(new UserDTO
{
IdUser = user.UserProfessionId,
UserName = user.UserName,
UserCompany = user.UserCompany,
UserMail = user.UserMail,
UserProfession = user.UserProfession,
UserProfessionField = user.UserProfessionField
});
}
return userDtos;
}
These are my model and DTO for user:
namespace Sims.Models
{
public partial class User
{
public User()
{
DataUsages = new HashSet<DataUsage>();
}
public long IdUser { get; set; }
public int UserProfessionId { get; set; }
public int UserProfessionFieldId { get; set; }
public string? UserName { get; set; }
public string? UserMail { get; set; }
public string? UserCompany { get; set; }
public byte[]? UserPicture { get; set; }
public virtual Profession UserProfession { get; set; } = null!;
public virtual ProfessionField UserProfessionField { get; set; } = null!;
public virtual ICollection<DataUsage> DataUsages { get; set; }
}
}
and
namespace sims.DTO
{
public partial class UserDTO
{
public long IdUser { get; set; }
public string? UserName { get; set; }
public string? UserMail { get; set; }
public string? UserCompany { get; set; }
public virtual ProfessionDTO UserProfession { get; set; } = null!;
public virtual ProfessionFieldDTO UserProfessionField { get; set; } = null!;
}
}
Profession and ProfessionField are also models and have their own DTO. But in the get method, the two following lines contain the same error as it "cannot implicitly convert type '....Models.Profession' to '....DTO.ProfessionDTO'".
Do you have any idea ?
In case, here is an example of the Profession Model and DTO:
namespace Sims.Models
{
public partial class Profession
{
public Profession()
{
ProfessionFields = new HashSet<ProfessionField>();
Users = new HashSet<User>();
}
public int IdProfession { get; set; }
public string ProfessionName { get; set; } = null!;
public virtual ICollection<ProfessionField> ProfessionFields { get; set; }
public virtual ICollection<User> Users { get; set; }
}
}
and
namespace sims.DTO
{
public class ProfessionDTO
{
public int IdProfession { get; set; }
public string ProfessionName { get; set; } = null!;
}
}
Thanks for reading
The UserProfession property is of type ProfessionDTO:
public virtual ProfessionDTO UserProfession { get; set; } = null!;
But you're trying to populate it with an object of type Profession:
UserProfession = user.UserProfession,
Just as the error states, they are different types and can't be substituted for one another. Populate the property with an instance of ProfessionDTO instead:
UserProfession = new UserProfessionDTO
{
IdProfession = user.UserProfession.IdProfession,
ProfessionName = user.UserProfession.ProfessionName
},
If the user.UserProfession field is null then you'd need to check for that. For example:
UserProfession = user.UserProfession == null ?
null as UserProfessionDTO :
new UserProfessionDTO
{
IdProfession = user.UserProfession?.IdProfession,
ProfessionName = user.UserProfession?.ProfessionName
},
Hello this is a mapping from a list (made by a SensorID field and a SensorValue) to a request model in which all values of the SensorID are reported as properties.
I did this:
public List<SensorStatus> SensorsStatus { get; set; }
public class SensorStatus
{
[Required]
public string SensorId { get; set; }
public string SensorValue { get; set; }
}
and the model request:
public class NotificheDiStatoModel
{
public int? qrStatus { get; set; }
public bool? qrEnabled { get; set; }
public int? clessStatus { get; set; }
public bool? clessEnabled { get; set; }
public int? magStatus { get; set; }
public bool? magEnabled { get; set; }
public int? puncherStatus { get; set; }
public bool? puncherEnabled { get; set; }
public int? bleStatus { get; set; }
public bool? bleEnabled { get; set; }
}
The mapping that works for now I did it this way,but I would like to improve the code
var listasensori = req.MessageBody.SensorsStatus;
NotificheDiStatoModel outputreq = new NotificheDiStatoModel();
foreach (var sensore in listasensori)
{
if (sensore.SensorId.Equals("blestatus"))
outputreq.bleStatus = Convert.ToInt32(sensore.SensorValue);
if (sensore.SensorId.Equals("bleenabled"))
outputreq.bleEnabled = Convert.ToBoolean(sensore.SensorValue);
if (sensore.SensorId.Equals("clessstatus"))
outputreq.clessStatus = Convert.ToInt32(sensore.SensorValue);
if (sensore.SensorId.Equals("clessenabled"))
outputreq.clessEnabled = Convert.ToBoolean(sensore.SensorValue);
if (sensore.SensorId.Equals("qrstatus"))
outputreq.qrStatus = Convert.ToInt32(sensore.SensorValue);
if (sensore.SensorId.Equals("qrenabled"))
outputreq.qrEnabled = Convert.ToBoolean(sensore.SensorValue);
if (sensore.SensorId.Equals("magstatus"))
outputreq.magStatus = Convert.ToInt32(sensore.SensorValue);
if (sensore.SensorId.Equals("magenabled"))
outputreq.magEnabled = Convert.ToBoolean(sensore.SensorValue);
if (sensore.SensorId.Equals("puncherstatus"))
outputreq.puncherStatus = Convert.ToInt32(sensore.SensorValue);
if (sensore.SensorId.Equals("puncherenabled"))
outputreq.puncherEnabled = Convert.ToBoolean(sensore.SensorValue);
};
Thanks in advance.
I would make your SensorId of enum type rather than string type:
public enum SensorIdEnum {
blestatus,
bleenabled,
clessstatus,
...
}
Your SensorStatus class would become:
public class SensorStatus
{
[Required]
public SensorIdEnum SensorId { get; set; }
public string SensorValue { get; set; }
}
This would reduce risk of typo and make documentation and extension easier.
Then, I would use generics to enforce a type on the SensorValue property right from the start.
public class SensorStatus<T>
{
[Required]
public SensorIdEnum SensorId { get; set; }
public T SensorValue { get; set; }
}
This way you're making the data conversion the responsibility of the SensorStatus itself (in its constructor maybe).
And you could make your outputreq basically a Dictionary<SensorIdEnum, object> that you pass around instead of a full fledged NotificheDiStatoModel class that you don't necessarily fully use:
var listasensori = req.MessageBody.SensorsStatus;
var outputreq = new Dictionary<SensorIdEnum, object>();
foreach (var sensore in listasensori)
{
outputreq[sensore.SensorId] = sensore.SensorValue;
};
Can be simplified like this. Just add all the strings that you checked in the Equals in a list. Example below
NotificheDiStatoModel outputreq = new NotificheDiStatoModel();
var senors = new List<SensorStatus>();
var acceptedSensorIds = new List<string> {"blestatus","bleenabled","clessstatus","clessenabled","qrstatus"};
foreach (var sensore in senors)
{
acceptedSensorIds.ForEach(id =>
{
if (sensore.SensorId.Equals(id, StringComparison.OrdinalIgnoreCase))
{
var propertyInfo = outputreq.GetType().GetProperty(id);
propertyInfo.SetValue(outputreq,Convert.ToBoolean(sensore.SensorValue));
}
});
}
I have 2 classes .
first class is
public class Client : BaseEntity
{
public Client()
{
ReportingPeriods = new List<ReportingPeriod>();
UserAccesses = new List<UserClientAccess>();
GraphColours = new List<GraphColour>();
RangeColours = new List<RangeColour>();
Properties = new List<PropertyMaster>();
PropertyCustomFilterHeaderMasters = new List<PropertyCustomFilterHeaderMaster>();
InterestCustomFilterHeaderMasters = new List<InterestCustomFilterHeaderMaster>();
Comments = new List<Comment>();
CustomDashboards = new List<CustomDashboard>();
Bookmarks = new List<Bookmark>();
}
public string Name { get; set; }
[IgnoreComparing]
public byte[] Logo { get; set; }
[Display(Name = "Logo Name")]
public string LogoName { get; set; }
public ClientStatus Status { get; set; }
[IgnoreComparing]
public virtual ICollection<ReportingPeriod> ReportingPeriods { get; set; }
[IgnoreComparing]
public virtual ICollection<UserClientAccess> UserAccesses { get; set; }
public virtual ICollection<PropertyCustomFilterHeaderMaster> PropertyCustomFilterHeaderMasters { get; set; }
[IgnoreComparing]
public virtual ICollection<InterestCustomFilterHeaderMaster> InterestCustomFilterHeaderMasters { get; set; }
[IgnoreComparing]
public virtual ICollection<Comment> Comments { get; set; }
}
and the second class is clientDto
public class ClientDto : IdModel
{
public string Name { get; set; }
public string AreaUnit { get; set; }
public string Currency { get; set; }
}
Can i map this two classes with automapper with this way
cfg.CreateMap<Client, ClientDto>();
I'm trying to create mapping, everything works fine but app throws exception
{"LINQ to Entities does not recognize the method 'System.String ToString()' method, and this method cannot be translated into a store expression."}
Here is the linq query
public async Task<PagedResultOutput<ClientDto>> GetClients(GetClientsInput input)
{
var clients = Queryable().AsNoTracking();
var totalCount = await clients.CountAsync();
clients = clients.OrderBy(input.Sorting)
.Skip(input.SkipCount)
.Take(input.MaxResultCount);
return new PagedResultOutput<ClientDto>(totalCount, await clients.ProjectToListAsync<ClientDto>(_mapperConfiguration));
}
I had a similar problem with a dictionary - now I'm trying to populate a viewmodel, to return a JSON object to a GET request.
My viewmodel is:
public class HotelInventoryta
{
public int api_version { get; set; }
public string lang { get; set; }
public List<Hotel_List_ta> hotels { get; set; }
}
public class Hotel_List_ta
{
public int ta_id { get; set; }
public string partner_id { get; set; }
public string name { get; set; }
public string street { get; set; }
public string city { get; set; }
public string postal_code { get; set; }
public string state { get; set; }
public string country { get; set; }
public double latitude { get; set; }
public double longitude { get; set; }
public string desc { get; set; }
public string url { get; set; }
public string email { get; set; }
public string phone { get; set; }
public string fax { get; set; }
}
My DataBase model is:
[Table("tblHotel")]
public class Hotelta
{
[Key()]
[Column("hotel_id")]
public long hotel_id { get; set; }
public string hotel_name { get; set; }
public string hotel_add1 { get; set; }
public string hotel_towncity { get; set; }
public string hotel_pc { get; set; }
public string hotel_country { get; set; }
public string hotel_pass { get; set; }
public string hotel_email { get; set; }
public string hotel_tel { get; set; }
public string hotel_fax { get; set; }
}
My controller code to populate the viewmodel is:
private HoteltaContext dbh = new HoteltaContext();
//
// GET: /ta/hotel_inventory
[HttpGet]
public HotelInventoryta hotel_inventory(int api_version, string lang)
{
{
HotelInventoryta hotelinventory = new HotelInventoryta();
hotelinventory.api_version = api_version;
hotelinventory.lang = lang;
// Get data from database
var h = dbh.Hotelta.Where(x => x.hotel_id != 0).ToList();
// loop through each result, and add it to the hotelinventory.hotels model
foreach (var ht in h)
{
// I get the exception on the next line
hotelinventory.hotels.Add(new Hotel_List_ta
{
ta_id = 0,
partner_id = ht.hotel_id.ToString(),
name = ht.hotel_name,
street = ht.hotel_add1,
city = ht.hotel_towncity,
postal_code = ht.hotel_pc,
country = ht.hotel_country,
url = "http://www.me.com",
email = ht.hotel_email,
phone = ht.hotel_tel,
fax = ht.hotel_fax
});
}
return hotelinventory;
}
}
The error is:
Object reference not set to an instance of an object
Firstly, can you help me resolve the error - and if possible, confirm if the way I am reading from the database and populating the viewmodel, is the best way to do it?
Thank you, Mark
This is because the hotels property is never initialized. You could do this in the constructor of HotelInventoryta:
public class HotelInventoryta
{
public HotelInventoryta()
{
hotels = new List<Hotel_List_ta>();
}
// ...
}
Now you initialzed the property with an empty collection, so you can add items to it, rather than hotels being null which causes your exception.
The source class:
public class Post
{
public long ID { get; set; }
[Column(TypeName="nvarchar")]
[Required]
[StringLength(250)]
public string Name { get; set; }
[Column(TypeName="varchar")]
[StringLength(250)]
public string UrlName { get; set; }
[Column(TypeName="ntext")]
public string Excerpt { get; set; }
[Column(TypeName="ntext")]
[Required]
public string Content { get; set; }
public DateTime PostedTime { get; set; }
public DateTime? PublishedTime { get; set; }
public DateTime? LastUpdatedTime { get; set; }
public bool IsPublished { get; set; }
public virtual List<Category> Categories { get; set; }
public virtual List<Comment> Comments { get; set; }
public virtual List<Tag> Tags { get; set; }
}
the destination class
public class Post : Model
{
public long ID { get; set; }
public string Name { get; set; }
public string UrlName { get; set; }
public string Excerpt { get; set; }
public string Content { get; set; }
public DateTime PostedTime { get; set; }
public DateTime LastCommentedTime { get; set; }
public bool IsPublished { get; set; }
public List<Category> Category { get; set; }
public List<Comment> Comments { get; set; }
public List<Tag> Tags { get; set; }
}
I try using EmitMapper to map from each other; when mapping from source to desction, here is the code sample:
[TestMethod]
public void ShouleMapEntityToModel()
{
Post eP = new Post();
eP.ID = 2;
eP.Comments = new List<Comment>();
eP.Comments.Add(new Comment()
{
ID = 2,
Author = "derek"
});
var mP = eP.Map<Post, mBlog.Core.Models.Post>();
Assert.IsNotNull(mP);
Assert.AreEqual(1, mP.Comments.Count());
}
and I got an exception,
Test method mBlog.Test.EmitMapperTest.ShouleMapEntityToModel threw exception:
System.Exception: Constructor for types [] not found in System.Collections.Generic.IList`1[[mBlog.Core.Models.Post, mBlog.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]
I had the same problem, but I have found the solution. Don't user Lists for your destination object. If you use simple arrays in your mBlog.Core.Models.Post object you should get a nicely filled object. So your destination class should look like:
public class Post : Model
{
public long ID { get; set; }
public string Name { get; set; }
public string UrlName { get; set; }
public string Excerpt { get; set; }
public string Content { get; set; }
public DateTime PostedTime { get; set; }
public DateTime LastCommentedTime { get; set; }
public bool IsPublished { get; set; }
public Category[] Category { get; set; }
public Comment[] Comments { get; set; }
public Tag[] Tags { get; set; }
}
This answer shows how to handle IEnumerable to IEnumerable: EmitMapper and List
I believe that can be applied to this case too. Take a look:
This can be done creating a custom class, implementing the interface "ICustomConverterProvider" and adding a ConvertGeneric to the "DefaultMapConfig".
Looking on the source code of EmitMapper, i found a class named "ArraysConverterProvider", which is the default generic converter from ICollections to Arrays.
Adapting the code from this class to work with IEnumerable
collections:
class GenericIEnumerableConverterProvider : ICustomConverterProvider
{
public CustomConverterDescriptor GetCustomConverterDescr(
Type from,
Type to,
MapConfigBaseImpl mappingConfig)
{
var tFromTypeArgs = DefaultCustomConverterProvider.GetGenericArguments(from);
var tToTypeArgs = DefaultCustomConverterProvider.GetGenericArguments(to);
if (tFromTypeArgs == null || tToTypeArgs == null || tFromTypeArgs.Length != 1 || tToTypeArgs.Length != 1)
{
return null;
}
var tFrom = tFromTypeArgs[0];
var tTo = tToTypeArgs[0];
if (tFrom == tTo && (tFrom.IsValueType || mappingConfig.GetRootMappingOperation(tFrom, tTo).ShallowCopy))
{
return new CustomConverterDescriptor
{
ConversionMethodName = "Convert",
ConverterImplementation = typeof(GenericIEnumerableConverter_OneTypes<>),
ConverterClassTypeArguments = new[] { tFrom }
};
}
return new CustomConverterDescriptor
{
ConversionMethodName = "Convert",
ConverterImplementation = typeof(GenericIEnumerableConverter_DifferentTypes<,>),
ConverterClassTypeArguments = new[] { tFrom, tTo }
};
}
}
class GenericIEnumerableConverter_DifferentTypes<TFrom, TTo> : ICustomConverter
{
private Func<TFrom, TTo> _converter;
public IEnumerable<TTo> Convert(IEnumerable<TFrom> from, object state)
{
if (from == null)
{
return null;
}
TTo[] result = new TTo[from.Count()];
int idx = 0;
foreach (var f in from)
{
result[idx++] = _converter(f);
}
return result;
}
public void Initialize(Type from, Type to, MapConfigBaseImpl mappingConfig)
{
var staticConverters = mappingConfig.GetStaticConvertersManager() ?? StaticConvertersManager.DefaultInstance;
var staticConverterMethod = staticConverters.GetStaticConverter(typeof(TFrom), typeof(TTo));
if (staticConverterMethod != null)
{
_converter = (Func<TFrom, TTo>)Delegate.CreateDelegate(
typeof(Func<TFrom, TTo>),
null,
staticConverterMethod
);
}
else
{
_subMapper = ObjectMapperManager.DefaultInstance.GetMapperImpl(typeof(TFrom), typeof(TTo), mappingConfig);
_converter = ConverterBySubmapper;
}
}
ObjectsMapperBaseImpl _subMapper;
private TTo ConverterBySubmapper(TFrom from)
{
return (TTo)_subMapper.Map(from);
}
}
class GenericIEnumerableConverter_OneTypes<T>
{
public IEnumerable<T> Convert(IEnumerable<T> from, object state)
{
if (from == null)
{
return null;
}
return from;
}
}
This code is just a copy with a minimum of adaptation as possible and
can be applyed to objects with many levels of hierarchy.
You can use the above code with the following command:
new DefaultMapConfig().ConvertGeneric(
typeof(IEnumerable<>),
typeof(IEnumerable<>),
new GenericIEnumerableConverterProvider());
This saved my day and I hope to save yours too! hehehe