Entity Framework class instantiation behavior - c#

I am facing a problem where my entity framework keeps behaving weirdly when I try to instantiate any class. I am trying to use the default usermanager behaviour to store data in my database. Once I start instantiating a new notification before it adds that notification to the list of notifications available in the application user class it persists those changes and process an error from entity framework saying that there is a multiplicity problem, how can I tell entity framework to not persist changes once I instantiate a class cf here is my controller code :
public string AddFriend(string AddedUserId)
{
var AddedUser = UserManager.FindById(AddedUserId);
var AddingUser = UserManager.FindById(User.Identity.GetUserId());
var friendship = new Friend(AddingUser, AddedUser) { IsInvitation = true };
AddingUser.Friends.Add(friendship);
AddedUser.Notifications.Add(new Notification(AddingUser, "Friend Invitation",
"The user " + AddingUser.FirstName + " " + AddingUser.LastName +
" Sent you a friend invitation", friendship));
UserManager.Update(AddedUser);
UserManager.Update(AddingUser);
return "Friend was added successfully";
}
my Notification class :
[Table("Notifications")]
public class Notification
{
[Key]
public int NotificationId { get; set; }
public string Title { get; set; }
public string Content { get; set; }
[Required]
public ApplicationUser AssociatedUser { get; set; }
public Friend AssociatedFrienship { get; set; }
public GroupMember AssociatedGroup { get; set; }
public ChannelMember AssociatedChannel { get; set; }
public Message AssociatedMessage { get; set; }
public bool Consulted { get; set; }
public Notification()
{
}
public Notification(ApplicationUser associatedUser, string title, string content, Friend associatedFriend = null, GroupMember associatedGroup = null, ChannelMember associatedChannel = null, Message associatedMessage = null)
{
AssociatedUser = associatedUser;
Title = title;
Content = content;
AssociatedChannel = associatedChannel;
AssociatedGroup = associatedGroup;
AssociatedFrienship = associatedFriend;
AssociatedMessage = associatedMessage;
Consulted = false;
}
}
my ApplicationUser class:
public partial class ApplicationUser : IdentityUser
{
public virtual List<Notification> Notifications { get; set; }
}
my FluentAPI code :
modelBuilder.Entity<Notification>()
.HasRequired(c => c.AssociatedUser)
.WithMany(c => c.Notifications);
Thanks in advance!

You don't have any foreign key to link AssociatedUser. Try adding a property to link Notification to ApplicationUser using foreign key. Like
public class Notification
{
//rest of properties
//change int to whatever type your primary key is in
//ApplicationUser class. I have Guid for example.
public int ApplicationUser AssociatedUserId {get;set;}
}
Then try modifying configuration like this:
modelBuilder.Entity<Notification>()
.HasRequired(c => c.AssociatedUser)
.WithMany(c => c.Notifications)
.HasForeignKey(n=>n.AssociatedUserId);

Related

Foreign table foriegn key value always showing null ef core 3.1

eWhy is my Icollection foreign key always blank I have a foreign table called photos which I have created using the Icollection. Im using ef core 3.1.7 and asp.net core 3.1 how does one get the file attachments VesselsId not to be null
Basically one vessel can have many photos but their could also be many vessels.
public class Vessels {
public int Id { get; set; }
[StringLength(400)]
public string Name { get; set; }
public string FlagName { get; set; }
public ICollection<FileAttachments> PhotosAttachments { get; set; }
}
This is the file attachments
public class FileAttachments {
public int Id { get; set; }
public string Title { get; set; }
public string File { set; get; }
}
In where I Wish to display the photos their blank I use the include statement to try and include them in my query.
var vessels = await _context.Vessels.Where(w=>w.Id==id).Include(c=>c.PhotosAttachments).FirstAsync();
But If I look here it will show PhotosAttachments being of null when I look at the field value sure enough its sitting there null.
I think i need to do something here but im not sure as to what
protected override void OnModelCreating(ModelBuilder modelBuilder) {
base.OnModelCreating(modelBuilder);
}
Edit 2
Basically i have a generic Upload Files method as such which is called via the submit button
[HttpPost]
public async Task<IActionResult> UploadFiles(List<IFormFile> FormFile, int UploadArea, int PoiId, int VesselId) {
FileAttachments attachments = new FileAttachments {
DocumentPath = filePath,
UploadAreaId = UploadArea,
CaseId = resultCaseId,
FullPath = savedFileName,
FileSize = infoFile.Length,
OrignalFileName = fileAttachments.FileName,
FileAttachmentType = fileAttachmentType,
TennantId = await GetCurrentTennantId(),
Extension = infoFile.Extension.Replace(".", "").ToLower(),
UploadedBy = caseOfficer.Id,
CreatedDate = DateTime.Now,
File = uniqueFilename,
ContentType = fileAttachments.ContentType,
isActive = true,
isDeleted = false
};
if (PoiId != 0) {
attachments.PoiID= PoiId;
}
if (VesselId != 0) {
attachments.VesselId = VesselId;
}
_context.Add(attachments);
await _context.SaveChangesAsync();
}
There is some confusion above i am using to store something else the
collection does not create this field it creates VesselsId with the
extra s this is what is not being filled in.
public int? VesselId { get; set; }
The collection creates this field
Add relation to FileAttachments model like this
public class FileAttachments {
...
[ForeignKey("Vessels")]
public int? VesselId { get; set; }
public Vessels Vessels { get; set; }
}

Entity Framework Include/select only certain properties from different tables

Let's say I have a method called GetThreadWithComments(). Each thread has 1 user (the creator) and has a list of comments. Each comments has 1 user (the poster).
Here are the classes (generated by EF):
public class Thread
{
public int ThreadId { get; set; }
public int UserId { get; set; }
public string Message { get; set; }
public List<Comment> Comments { get; set; }
public User User { get; set; }
}
public class Comment
{
public long CommentId { get; set; }
public string Message { get; set; }
public int UserId { get; set; }
public int ThreadId { get; set; }
public User User { get; set; }
}
So basically, I want to load a thread with user info, and associated comments with user info. I've tried something like this:
db.Threads.Select(x => new
{
x,
x.User = new { x.User.Username, x.User.Email },
x.Comments = x.Comments.Select(c => new
{
c.Message,
c.CommentId,
c.User = new { c.User.Username, c.User.Email }
})
});
The above does not work. However, I am not too sure on how to correctly do this. I could use include, but that would generate all properties. Since speed is a concern, I am trying to keep things as light as possible.
Reason it does not work: it does not build. Compile time error. The 2 errors I get are:
Cannot implicitly convert type '' to...
and
CS0746 Invalid anonymous type member declarator. Anonymous type members must be declared with a member assignment, simple name or member access.
First, define entity relationships as virtual, for example
public User User { get; set; }
should be
public virtual User User { get; set; }
Second, in case of the later posted compiler error, try adding the member names.
So instead of
x.User = new { x.User.Username, x.User.Email }
use
x.User = new { Username = x.User.Username, Email = x.User.Email }
Also there is too much x in there. The corrected example would be:
db.Threads.Select(x => new
{
x,
User = new { Username = x.User.Username, Email = x.User.Email },
Comments = x.Comments.Select(c => new
{
c.Message,
c.CommentId,
User = new { Username = c.User.Username, Email = c.User.Email }
})
});
Try this,
var result = db.Threads.Include(thread => thread.Comments);
Hope helps,

Storing list of custom objects in Entity Framework

Started to learn asp.net and DB manipulations. Trying to implement some simple functionality - two models, one has list of references to another.
Here is an error that I currently get:
An exception occurred while initializing the database. See the InnerException for details.
Inner exception:
Unable to determine a valid ordering for dependent operations. Dependencies may exist due to foreign key constraints, model requirements, or store-generated values.
My models:
public class Killer
{
public Killer(string name, string biography)
{
Name = name;
Biography = biography;
KillerId = Guid.NewGuid();
}
public Guid KillerId { get; set; }
public string Name { get; set; }
public string Biography { get; set; }
public virtual Contract Contract { get; set; }
}
public class Contract
{
public Contract(Status status, Killer target, string description, params Killer[] targets)
{
ContractId = Guid.NewGuid();
this.status = status;
Target = target;
Description = description;
Killers = new HashSet<Killer>();
foreach (var t in targets) Killers.Add(t);
}
public Guid ContractId { get; set; }
public enum Status { active, done, failed, rejected, abandoned }
public Status status { get; set; }
public Killer Target { get; set; }
public string Description { get; set; }
//[ForeignKey("ContractID")]
public virtual ICollection<Killer> Killers { get; set; }
}
In context I initialize db with lists of objects
public class KillerContext : DbContext
{
public DbSet<Killer> Killers { get; set; }
public DbSet<Contract> Contracts { get; set; }
}
In controller I do:
KillerContext k = new KillerContext();
public ActionResult Index()
{
var contracts = k.Contracts.ToList();
ViewBag.contracts = contracts;
return View();
}
In Global.asax:
Database.SetInitializer(new KillerContextInitialization());
Here is how I enter first data in db:
public sealed class KillerContextInitialization : DropCreateDatabaseAlways<KillerContext>
{
protected override void Seed(KillerContext db)
{
List<Killer> killers = new List<Killer>();
//List<Contract> contracts = new List<Contract>();
killers.Add(new Killer(name: "Ivan Firstein", biography: "He was born in the shadows."));
killers.Add(new Killer(name: "Oleg Gazmanov", biography: "test man"));
db.Contracts.Add(new Contract(
Contract.Status.active,
killers.SingleOrDefault(x => x.Name == "Ivan Firstein"),
"KILL OR BE KILLED. As always with love.",
killers.SingleOrDefault(x => x.Name == "Oleg Gazmanov")
));
db.Killers.AddRange(killers);
base.Seed(db);
}
}
Looks like you need add ForeignKey attribute for killer Model, and store this key in property ContractId:
public class Killer
{
[ForeignKey(nameof(ContractId)] //Name of added property in line below
public Contract Contract { get; set; } //no need "virtual"
public Guid? ContractId { get; set; }
// other properties...
}
public class Contract
{
[ForeignKey("ContractId")] //Name of added property in Killer Model
public virtual ICollection<Killer> Killers { get; set; }
// other code...
}
EDIT
You should do something similar to the Contract.Target property:
[ForeignKey(nameof(TargetId)]
public Killer Target { get; set; }
public Guid TargetId { get; set; }
For enum types you should add attributes like this:
[Column(nameof(status), TypeName = "int")]
public Status status { get; set; }
Find out that problem was in public Killer Target { get; set; }
When i was adding data, that field was considered as NOT NULL, and all what i need to do, is save changes after filling killers, like so:
public sealed class KillerContextInitialization : DropCreateDatabaseAlways<KillerContext>
{
protected override void Seed(KillerContext db)
{
List<Killer> killers = new List<Killer>();
killers.Add(new Killer(name: "Ivan Firstein", biography: "He was born in the shadows."));
killers.Add(new Killer(name: "Oleg Gazmanov", biography: "test man"));
db.SaveChanges(); // - save killers first, then add them to contract
db.Contracts.Add(new Contract(
Contract.Status.active,
killers.SingleOrDefault(x => x.Name == "Ivan Firstein"),
"KILL OR BE KILLED. As always with love.",
killers.SingleOrDefault(x => x.Name == "Oleg Gazmanov")
));
db.Killers.AddRange(killers);
base.Seed(db);
}
}

DbSet.Add() not working

I have a class like this:
[Table("member_activation")]
public partial class MemberActivation
{
[Key]
public Int64 member_id { get; set; }
public String token { get; set; }
}
My db:
public class SMADbContext : DbContext
{
public SMADbContext() : base("SMADB")
{
Database.SetInitializer<SMADbContext>(new NullDatabaseInitializer<SMADbContext>());
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
}
public DbSet<Member> Members { get; set; }
public DbSet<MemberActivation> MemberActivations { get; set; }
public DbSet<ApiAccount> ApiAccounts { get; set; }
public DbSet<ApiHardware> ApiHardwares { get; set; }
public DbSet<MemberRelation> MemberRelations { get; set; }
}
In my controller:
[Route("tester")]
[AllowAnonymous]
public IHttpActionResult tester()
{
using (var db = new SMADbContext())
{
var memberActivation = new MemberActivation();
memberActivation.member_id = 10155;
memberActivation.token = "hello";
db.MemberActivations.Add(memberActivation);
return Json(new { dbset = db.MemberActivations.ToList(), memberAct = memberActivation });
}
}
db.MemberActivations.Add(memberActivation); does not work. When I return the json, the dbset does not include the newly created memberActivation. I do not have db.SaveChanges() because it will not save until the memberActivation is pushed to the dbset
You cant set member_id, it is the key and ef uses it as identity. It will be ignored. You can configure ef so that member_id is not identity but that's another topic.
db.MembershipActivations.Add( new MemberActivation { token = "hello"}):
db.SaveChanges();
should work fine.
if however , as it would appear , you have an existing member and you are trying to set a relationship with that entity via a join table. Then you should retrieve that entity and set the memberactivation. Ef will sort the rest out for you. Bit of guessing here as i would need to see the involved entities.

EF adding duplicate records into lookup/reference table

I have 3 tables,
1. AttributeTypes (Columns: AttributeId (PK), AttributeName, ..)
2. Location (Columns: locationId (PK), LocationName, ...)
3. LocationAttributeType (Columns: locationId (FK), AttributeId (FK))
Whenever I am trying to insert new location record along with its attribute type from GUI, it should create new record for Table- Location and LocationAttributeType. But EF trying to add new record in Table- AttributeTypes as well, which is just used as reference table and should not add new/duplicate records in it. How can I prevent that?
here is my code,
The model which GUI sends is,
public class LocationDataModel
{
[DataMember]
public int Id { get; set; }
[DataMember]
public string Code { get; set; }
[DataMember]
public List<AttributeTypeDataModel> AssignedAttributes = new List<AttributeTypeDataModel>();
}
public class AttributeTypeDataModel
{
protected AttributeTypeDataModel() {}
public AttributeTypeDataModel(int id) { this.Id = id; }
public AttributeTypeDataModel(int id, string name)
: this(id)
{
this.Name = name;
}
[DataMember]
public int Id { get; set; }
[DataMember]
public string Name { get; set; }
[DataMember]
public virtual ICollection<LocationDataModel> Locations { get; set; }
}
The Entities created by EF are,
public partial class Location
{
public Location()
{
this.AttributeTypes = new List<AttributeType>();
}
public Location(int campusId, string code)
: this()
{
CampusId = campusId; Code = code;
}
public int Id { get; set; }
public int CampusId { get; set; }
public string Code { get; set; }
public virtual ICollection<AttributeType> AttributeTypes { get; set; }
}
public partial class AttributeType
{
public AttributeType()
{
this.Locations = new List<Location>();
}
public int AttributeTypeId { get; set; }
public string AttributeTypeName { get; set; }
public virtual ICollection<Location> Locations { get; set; }
}
I have below code to Add these new location to database,
private IEnumerable<TEntity> AddEntities<TModel, TEntity, TIdentityType>
(IEnumerable<TModel> models, Func<TModel, TIdentityType> primaryKey,
IGenericRepository<TEntity, TIdentityType> repository)
{
var results = new List<TEntity>();
foreach (var model in models)
{
var merged = _mapper.Map<TModel, TEntity>(model);
var entity = repository.Upsert(merged);
results.Add(entity);
}
repository.Save();
return results.AsEnumerable();
}
I am using following generic repository to do entity related operations
public TEntity Upsert(TEntity entity)
{
if (Equals(PrimaryKey.Invoke(entity), default(TId)))
{
// New entity
return Context.Set<TEntity>().Add(entity);
}
else
{
// Existing entity
Context.Entry(entity).State = EntityState.Modified;
return entity;
}
}
public void Save()
{
Context.SaveChanges();
}
Whats wrong I am doing here?
The DbSet<T>.Add() method attaches an entire object graph as added. You need to indicate to EF that the 'reference' entity is actually already present. There are two easy ways to do this:
Don't set the navigation property to an object. Instead, just set the corresponding foreign key property to the right value.
You need to ensure that you don't load multiple instances of the same entity into your object context. After creating the context, load the full list of AttributeType entities into the context and create a Dictionary<> to store them. When you want to add an attribute to a Location retrieve the appropriate attribute from the dictionary. Before calling SaveChanges() iterate through the dictionary and mark each AttributeType as unchanged. Something like this:
using (MyContext c = new MyContext())
{
c.AttributeTypes.Add(new AttributeType { AttributeTypeName = "Fish", AttributeTypeId = 1 });
c.AttributeTypes.Add(new AttributeType { AttributeTypeName = "Face", AttributeTypeId = 2 });
c.SaveChanges();
}
using (MyContext c = new MyContext())
{
Dictionary<int, AttributeType> dictionary = new Dictionary<int, AttributeType>();
foreach (var t in c.AttributeTypes)
{
dictionary[t.AttributeTypeId] = t;
}
Location l1 = new Location(1, "Location1") { AttributeTypes = { dictionary[1], dictionary[2] } };
Location l2 = new Location(2, "Location2") { AttributeTypes = { dictionary[1] } };
// Because the LocationType is already attached to the context, it doesn't get re-added.
c.Locations.Add(l1);
c.Locations.Add(l2);
c.SaveChanges();
}
In this specific case you are using a many-to-many relationship, with EF automatically handling the intermediate table. This means that you don't actually have the FK properties exposed in the model, and my first suggestion above won't work.
Therefore, you either need to use the second suggestion, which still ought to work, or you need to forgo the automatic handling of the intermediate table and instead create an entity for it. This would allow you to apply the first suggestion. You would have the following model:
public partial class Location
{
public Location()
{
this.AttributeTypes = new List<LocationAttribute>();
}
public Location(int campusId, string code)
: this()
{
CampusId = campusId; Code = code;
}
public int Id { get; set; }
public int CampusId { get; set; }
public string Code { get; set; }
public virtual ICollection<LocationAttribute> AttributeTypes { get; set; }
}
public partial class LocationAttribute
{
[ForeignKey("LocationId")]
public Location Location { get; set; }
public int LocationId { get; set; }
public int AttributeTypeId { get; set; }
}
public partial class AttributeType
{
public int AttributeTypeId { get; set; }
public string AttributeTypeName { get; set; }
}
With this approach you do lose functionality since you can't navigate from a Location to an AttributeType without making an intermediate lookup. If you really want to do that, you need to control the entity state explicitly instead. (Doing that is not so straightforward when you want to use a generic repository, which is why I've focused on this approach instead.)
Thank you all for your suggestions.
I have to get rid of my generic repository here to save my context changes and do it manually as below,
private IEnumerable<int> AddLocationEntities(IEnumerable<LocationDataModel> locations)
{
var results = new List<int>();
foreach (LocationDataModel l in locations)
{
var entity = _mapper.Map<LocationDataModel, Location>(l);//you can map manually also
var AttributeCode = l.AssignedAttributes.FirstOrDefault().AttributeTypeId;
using (MyContext c = new MyContext())
{
var attr = c.AttributeTypes.Where(a => a.Id == AttributeTypeId ).ToList();
entity.AttributeTypes = attr;
c.Locations.Add(entity);
c.SaveChanges();
var locid = entity.Id;
results.Add(locid);
}
}
return results;
}
In the else statement of yourUpsert you should add
context.TEntity.Attach(entity);

Categories

Resources