ASP.NET model that requires a reference to a IdentityUser? - asp.net-core

I have a model Blueprint that requires a reference to an IdentityUser. When I run Add-Migration CreateBlueprintSchema the following error is thrown:
No suitable constructor was found for entity type 'Blueprint'. The following constructors had parameters that could not be bound to properties of the entity type: cannot bind 'author' in 'Blueprint(IdentityUser author, string name, string data)'.
How do I resolve this issue?
Blueprint.cs
using Microsoft.AspNetCore.Identity;
using System.ComponentModel.DataAnnotations;
using System.Security.Cryptography;
using System.Text;
namespace FactorioStudio.Models
{
public class Blueprint
{
[MaxLength(40)]
public string Id { get; set; }
[Required]
public string Name { get; set; }
[Required]
public string Data { get; set; }
[Required]
public IdentityUser Author { get; set; }
[Required]
public DateTime CreationDate { get; set; }
public Blueprint? ParentBlueprint { get; set; }
public Blueprint(IdentityUser author, string name, string data)
{
Author = author;
Name = name;
Data = data;
CreationDate = DateTime.UtcNow;
string hashSource = Author.Id +
Name +
CreationDate.ToString("s", System.Globalization.CultureInfo.InvariantCulture) +
Data;
using SHA1 sha1 = SHA1.Create();
byte[] hashBytes = sha1.ComputeHash(Encoding.UTF8.GetBytes(hashSource));
string hash = BitConverter.ToString(hashBytes).Replace("-", String.Empty).ToLower();
Id = hash;
}
}
}

change your model like this:
public class Blueprint
{
[MaxLength(40)]
public string Id { get; set; }
[Required]
public string Name { get; set; }
[Required]
public string Data { get; set; }
[Required]
public IdentityUser Author { get; set; } = new IdentityUser();
//.......
}
or just add a non-parameter constructor in your model.
You can refer to this;
Edit=======================
For testing convenience, I manually added a foreign key(If I don't add Fk by myself, EF core will create shadow foreign key property, But i can't use it directly), Then I create a Blueprint without referring to an existing IdentityUser, You can see it will report a SqlException that show error in FK.

As I wrote in the comment, EF Core cannot set navigation properties using a constructor. Instead, use a custom value generator to create the hash for the ID when saving the entity.
public class BlueprintHashGenerator : ValueGenerator<string>
{
public override bool GeneratesTemporaryValues => false;
public override string Next(EntityEntry entry)
{
if (entry.Entity is not Blueprint bp)
{
throw new ApplicationException("Unexpected entity");
}
string hashSource = bp.Author.Id +
bp.Name +
bp.CreationDate.ToString("s", System.Globalization.CultureInfo.InvariantCulture) +
bp.Data;
using SHA1 sha1 = SHA1.Create();
byte[] hashBytes = sha1.ComputeHash(Encoding.UTF8.GetBytes(hashSource));
return BitConverter.ToString(hashBytes).Replace("-", String.Empty).ToLower();
}
}
then in model builder
builder.Entity<Blueprint>().Property(bp => bp.Id).HasValueGenerator<BlueprintHashGenerator>().ValueGeneratedNever();
It will generate the value on SaveChanges, so ensure all properties are set before calling save (Author, Name, CreationDate, Data).

Related

How to solve may not be abstract and must be include a default constructor?

I have a database [![as this picture][1]][1]
I want to get a chart from this database and when I tried to use SQL query on the controller side, I got an error like this ;
[![this is error type][2]][2]
This is my controller code ;
public ActionResult Index2(String makineadi, DateTime? startdate, DateTime? enddate)
{
var data = entities.Database.SqlQuery<DataPoint>("SELECT Sıcaklık, Recete_Sure From Recete Where Machine_IP ='" + makineadi + "' and Tarih between'"+startdate+"' and '"+enddate+"'").ToList();
ViewBag.DataPoints = JsonConvert.SerializeObject(data);
return View();
}
And here is my class definition for getting the chart to JSON serialization;
[DataContract]
public class DataPoint
{
public DataPoint(int Sıcaklık, int Recete_Sure)
{
this.Sıcaklık = Sıcaklık;
this.Recete_Sure = Recete_Sure;
}
//Explicitly setting the name to be used while serializing to JSON.
[DataMember(Name = "Sıcaklık")]
public Nullable<int> Sıcaklık { get; set; }
//Explicitly setting the name to be used while serializing to JSON.
[DataMember(Name = "Recete_Sure")]
public Nullable<int> Recete_Sure { get; set; }
}
What should I do to fix it?
[1]: https://i.stack.imgur.com/ObwWR.png
[2]: https://i.stack.imgur.com/zQNgO.png
Add default constructor in class.
[DataContract]
public class DataPoint
{
public DataPoint()
{
}
public DataPoint(int Sıcaklık, int Recete_Sure)
{
this.Sıcaklık = Sıcaklık;
this.Recete_Sure = Recete_Sure;
}
//Explicitly setting the name to be used while serializing to JSON.
[DataMember(Name = "Sıcaklık")]
public Nullable<int> Sıcaklık { get; set; }
//Explicitly setting the name to be used while serializing to JSON.
[DataMember(Name = "Recete_Sure")]
public Nullable<int> Recete_Sure { get; set; }
}
It will resolve issue

automapper unflatten excludes value from parent object

I've a situation where entity framework core (2.0) is performing additional work to a parent table when I update a row in a child table. I've pin-pointed the cause to a value not being set in the unflattened object tree produced by AutoMapper (I'm not saying it is an error in AutoMapper; it's probably more to do with my code).
I'm using ASP.NET Core 2.0, C#, EF Core 2.0 and AutoMapper for the API development side. The database already exists and the EF classes scaffolded from it.
To keep it short, the child table is Note and the parent table is NoteType. The EF classes (extraneous columns removed) are as follows :
//Entity classes
public partial class Note
{
public int NoteBookId { get; set; }
public short NoteSeqNo { get; set; }
public short NoteTypeId { get; set; }
public string NoteText { get; set; }
public NoteBook NoteBook { get; set; }
public NoteType NoteType { get; set; }
}
public partial class NoteType
{
public NoteType() { Note = new HashSet<Note>(); }
public short NoteTypeId { get; set; }
public string NoteTypeDesc { get; set; }
public ICollection<Note> Note { get; set; }
}
//DTO class
public class NoteDto
{
public int NoteBookId { get; set; }
public short NoteSeqNo { get; set; }
public short NoteTypeId { get; set; }
public string NoteTypeNoteTypeDesc { get; set; }
public string NoteText { get; set; }
}
public class NoteTypeDto
{
public short NoteTypeId { get; set; }
public string NoteTypeDesc { get; set; }
}
(NoteBookId + NoteSeqNo) is Note's primary key.
NoteTypeId is the NoteType's primary key.
Configuration
This is the AutoMapper configuration:
// config in startup.cs
config.CreateMap<Note,NoteDto>().ReverseMap();
config.CreateMap<NoteType,NoteTypeDto>().ReverseMap();
Read the data
As a result of data retrieval I get the expected result and the parent note type description is populated.
// EF get note in repository
return await _dbcontext.Note
.Where(n => n.NoteId == noteId && n.NoteSeqNo == noteSeqNo)
.Include(n => n.NoteType)
.FirstOrDefaultAsync();
// Get note function in API controller
Note note = await _repository.GetNoteAsync(id, seq);
NoteDto noteDto = Mapper.Map<NoteDto>(note);
Example JSON result:
{
"noteBookId": 29,
"noteSeqNo": 19,
"noteTypeId": 18,
"noteTypenoteTypeDesc": "ABCDE",
"noteText": "My notes here."
}
Update the data
When the process is reversed during an update, the API controller maps the dto to the entity
Mapper.Map<Note>(noteDto)
Then when it is passed to EF by the repository code, EF tries to add a NoteType row with id 0. The unflattened object tree looks like this:
Note
NoteBookId = 29
NoteSeqNo = 19
NoteTypeId = 18
NoteTypeNoteTypeDesc = "ABCDE"
NoteText = "My notes updated."
NoteType.NoteTypeDesc = "ABCDE"
NoteType.NoteTypeId = 0
The parent id column (NoteType.NoteTypeId) value is 0 and is not assigned the value of 18 which is what I expected.
(During debugging I manually set NoteType.NoteTypeId to 18 to ensure EF did nothing with it).
To work around this at the moment I nullify the NoteType in the Note in the repository code.
Should I expected AutoMapper to populate all the parent properties with setters or have I missed some configuration? Perhaps there is a glaring flaw in my approach?
When AutoMapper reverses the mapping, it has to collect all information for nested objects from the flat object. Your DTO only carries a value for the mapping NoteType -> NoteTypeDesc. Not for NoteType -> NoteTypeId, so AM really doesn't have any idea where to get that value from.
If you want to rely on flattening only, the only way to change that is to add a flattened NoteTypeId to the DTO besides the unflattened one:
public class NoteDto
{
public int NoteBookId { get; set; }
public short NoteSeqNo { get; set; }
public short NoteTypeId { get; set; } // Not flattened
public short NoteTypeNoteTypeId { get; set; } // Flattened
public string NoteTypeNoteTypeDesc { get; set; }
public string NoteText { get; set; }
}
The alternative is to add this to your mapping:
config.CreateMap<Note, NoteDto>()
.ForMember(dest => dest.NoteTypeId,
e => e.MapFrom(src => src.NoteType.NoteTypeId))
.ReverseMap();
MapFrom-s (including the default unflattening) are reversed now. You can drop ReverseMap and create the maps, ignore Note.NoteType or ignore the offending path, Note.NoteType.NoteTypeDesc.

Asp.Net Mvc, Rdlc reports - How do I show associated name from parent table instead of just it's ID?

I am using Asp.net MVC 5 with EF 6 for a college project. I am using RDLC for generating reports.
I created a simple report showing Items and its associated details. But I have no idea how to show Manufacturer Name of an Item instead of its ManufacturerID.
Could anyone help me out with this?
It has been so many days, and I am still struggling with reports. Looks like, its easy with webforms and ADO.net. But I am finding it hard to incorporate reports in MVC using Entity Framework.
If there are better options please let me know.
Well for this report I refered to this tutorial .
Here is my code for generating reports:
public ActionResult Report(string id)
{
LocalReport lr = new LocalReport();
string path = Path.Combine(Server.MapPath("~/Reports"), "Report_item.rdlc");
if (System.IO.File.Exists(path))
{
lr.ReportPath = path;
}
else
{
return View("Index");
}
List<Item> cm = new List<Item>();
cm = db.Items.ToList();
ReportDataSource rd = new ReportDataSource("MyData_Item", cm);
lr.DataSources.Add(rd);
string reportType = id;
string mimeType;
string encoding;
string fileNameExtension;
string deviceInfo =
"<DeviceInfo>" +
" <OutputFormat>" + id + "</OutputFormat>" +
" <PageWidth>8.5in</PageWidth>" +
" <PageHeight>11in</PageHeight>" +
" <MarginTop>0.5in</MarginTop>" +
" <MarginLeft>1in</MarginLeft>" +
" <MarginRight>1in</MarginRight>" +
" <MarginBottom>0.5in</MarginBottom>" +
"</DeviceInfo>";
Warning[] warnings;
string[] streams;
byte[] renderedBytes;
renderedBytes = lr.Render(
reportType,
deviceInfo,
out mimeType,
out encoding,
out fileNameExtension,
out streams,
out warnings);
return File(renderedBytes, mimeType);
}
Item Model :
public class Item
{
public int ID { get; set; }
[Required]
[Display(Name="Item")]
public string Name { get; set; }
[Display(Name="Generic Name")]
public int? DrugGenericNameID { get; set; }
[Display(Name = "Manufacturer")]
public int? ManufacturerID { get; set; }
[Display(Name = "Categeory")]
public Categeory? Categeory { get; set; }
public int AlertQty { get; set; }
public string Description { get; set; }
[Display(Name = "Last Update")]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
public DateTime? LastUpdated { get; set; }
//reference entity
public virtual DrugGenericName DrugGenericName { get; set; }
public virtual Manufacturer Manufacturer { get; set; }
public virtual ICollection<Stock> Stocks { get; set; }
public virtual ICollection<PurchaseItem> PurchaseItems { get; set; }
}
public enum Categeory
{
Drug,
Supplies,
other
}
Here is my report. How can I derive associated Names instead of its ID?
Generally RDLC reports use a fairly flat structure for their data model - you can have associated child tables within the report; but for a flat table in your report all of your fields should be in the same table/model.
So, consider this as your Item model (removed most of your original properties for brevity):
public class Item
{
public int ID { get; set; }
[Required]
[Display(Name="Item")]
public string Name { get; set; }
// Properties as per your original model ...
//reference entity
public virtual DrugGenericName DrugGenericName { get; set; }
public virtual Manufacturer Manufacturer { get; set; }
public virtual ICollection<Stock> Stocks { get; set; }
public virtual ICollection<PurchaseItem> PurchaseItems { get; set; }
// Added properties:
public string ManufacturerName {
get { return Manufacturer.Name; }
}
// etc.
}
You'll then be able to reference the Manufacturer Name directly.

Convert Model into a ViewModel [duplicate]

This question already has answers here:
Where to convert business model to view model?
(3 answers)
Closed 5 years ago.
I have a model for my table OWNER
namespace MyApp.Models
{
public partial class Owner
{
public int owner_id { get; set; }
public string firstname { get; set; }
public string lastname { get; set; }
public string address { get; set; }
public string city { get; set; }
public virtual User User { get; set; }
}
}
and then i have my ViewModel
public partial class OwnerData
{
public string firstname { get; set; }
public string lastname { get; set; }
public string address { get; set; }
}
My Controller
public ActionResult Index()
{
//Create the object Owner based on the User Identity
Owner owner = CustomDbFunctions.GetUserEntity(User, db).Owner;
//New instance of the ViewModel
OwnerData ownerData = new OwnerData();
ownerData.lastname = owner.lastname;
ownerData.firstname = owner.firstname;
ownerData.address = owner.address;
return View(ownerData);
}
There's a simpler way in order to convert my owner into ownerData without rewrite evry single property of my object?
I think i solved the problem using Automapper
public ActionResult Index()
{
//Create the object Owner based on the User Identity
Owner owner = CustomDbFunctions.GetUserEntity(User, db).Owner;
Mapper.CreateMap<Owner, OwnerData>();
OwnerData ownerData = Mapper.Map<Owner, OwnerData>(owner);
return View(ownerData);
}
Or if you prefer a dynamic function
static TDestination GenerateMappingTable<TSource, TDestination>(TSource inputModel)
{
Mapper.CreateMap<TSource, TDestination>();
return Mapper.Map<TSource, TDestination>(inputModel);
}
To install Automapper just execute
PM> Install-Package AutoMapper
from the package manager console.
You can use object initizalizers.That makes it more simple,but I'm not sure this is what you want
OwnerData ownerData = new OwnerData {
lastname = owner.lastname;
firstname = owner.firstname;
address = owner.address;
};

Can't correctly add associated objects into Entity Framework Context

I have and entity framework project exposed via a data service:
public class VersionContext : DbContext
{
public DbSet<VersionTreeEntry> VersionTreeEntries { get; set; }
public DbSet<PluginState> PluginStates { get; set; }
public static void SetForUpdates()
{
Database.SetInitializer(new MigrateDatabaseToLatestVersion<VersionContext, Configuration>());
}
}
public class VersionTreeEntry
{
public VersionTreeEntry()
{
Children = new List<VersionTreeEntry>();
PluginStates = new List<PluginState>();
}
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long Id { get; set; }
public virtual ICollection<VersionTreeEntry> Children { get; set; }
public virtual ICollection<PluginState> PluginStates { get; set; }
public virtual VersionTreeEntry Ancestor { get; set; }
/// <summary>
/// Links to the ProtoBufDataItem Id for the session state.
/// </summary>
public int DataId { get; set; }
public string Notes { get; set; }
[Required]
public DateTime TimeStamp { get; set; }
[MinLength(1, ErrorMessage = "Tag cannot have a zero length")]
[MaxLength(20, ErrorMessage = "A tag name cannot contain over 20 characters")]
public string Tag { get; set; }
public bool IsUiNodeExpanded { get; set; }
[Required]
public string Version { get; set; }
[Required]
public string SessionName { get; set; }
}
public class PluginState
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long Id { get; set; }
public string PluginName { get; set; }
[Required]
public byte[] Data { get; set; }
}
As far as I can see, the data classes are defined correctly. I try to create some new objects and add them into the context, with their relations intact:
var session = new Session();
session.SessionName = "My new session";
VersionTreeEntry versionTreeEntry = new VersionTreeEntry();
versionTreeEntry.SessionName = session.SessionName;
versionTreeEntry.Version = Assembly.GetExecutingAssembly().GetName().Version.ToString();
versionTreeEntry.TimeStamp = DateTime.Now;
_versionContext.AddToVersionTreeEntries(versionTreeEntry);
foreach (var plugin in session.Plugins)
{
using (var ms = new MemoryStream())
{
plugin.SaveState(ms);
PluginState state = new PluginState();
state.PluginName = plugin.PluginName;
state.Data = ms.ToArray();
versionTreeEntry.PluginStates.Add(state);
}
}
_versionContext.SaveChanges();
The problem is that the PluginState instances never actually get added to the database. If I add code to add them manually to the context, they do get added, but the foreign key pointing back to the VersionTreeEntry is null.
Again, this is a WCF DataService rather than vanilla EF, any idea what might be wrong?
Cheers
Posting the answer here from the comment section.
Agreed. The best way to do this is to call the following API:
_versionContext.AddRelatedObject(versionTreeEntry, "PluginStates", state);
Thanks
Pratik