I'm trying to get my head around EF7 by writing a simple master-detail relationship to a sqlite database. Saving works fine, reading however gives me headaches:
Here are my Entities:
public class Message
{
public int MessageId { get; set; }
public string Name { get; set; }
public List<MessagePart> MessageParts { get; set; }
}
public class MessagePart
{
public int MessagePartId { get; set; }
public string Text { get; set; }
public int MessageId { get; set; }
public Message Message { get; set; }
}
createMessage() does what it is supposed to:
static void createMessages()
{
using (var db = new TestContext())
{
var m1 = new Message
{
Name = "train_arrives_in_x_minutes",
MessageParts = new List<MessagePart>()
{
new MessagePart {
Text = "Train arrives in 5 minutes"
},
new MessagePart {
Text = "Zug faehrt in 5 Minuten ein",
}
}
};
var m2 = new Message
{
Name = "train_out_of_service",
MessageParts = new List<MessagePart>()
{
new MessagePart {
Text = "train is out of service"
},
new MessagePart {
Text = "Kein Service auf dieser Strecke",
}
}
};
db.Messages.Add(m1);
db.Messages.Add(m2);
var count = db.SaveChanges();
Console.WriteLine("{0} records saved to database", count);
}
}
Reading from an existing database reads the master record fine, but the detail recordset pointer stays null.
static void readMessages()
{
using (var db = new TestContext())
{
foreach (Message m in db.Messages)
{
Console.WriteLine(m.Name);
// exception here: m.MessageParts is always null
foreach(MessagePart mp in m.MessageParts)
{
Console.WriteLine("mp.Text={0}", mp.Text);
}
}
}
}
Is there anything I can do to force those messagesparts to load? I've worked with other (Python) ORMs before and never had this problem before. Is this a problem with Lazy Loading? I tried to fetch those childrecords using a LINQ statement, that didn't help either. Everything looks good in the database though.
If you want to enable LazyLoading you need to enable LazyLoading (should be enabled by default) and make your property virtual:
public TestContext()
: base(Name = "ConntextionName")
{
this.Configuration.ProxyCreationEnabled = true;
this.Configuration.LazyLoadingEnabled = true;
}
And your models shuodl look like:
public class Message
{
public int MessageId { get; set; }
public string Name { get; set; }
public virtual ICollection<MessagePart> MessageParts { get; set; }
}
public class MessagePart
{
public int MessagePartId { get; set; }
public string Text { get; set; }
public int MessageId { get; set; }
public virtual Message Message { get; set; }
}
If you do not want to use LazyLoading, you can load related entities using eager loading:
using System.Data.Entity;
using (var db = new TestContext())
{
int messageId = ....;
Message message = db.Messages
.Where(m => m.MessageId == messageId)
.Include(m => m.MessageParts) // Eagerly load message parts
.FirstOrDefault();
// Your message and all related message parts are now loaded and ready.
}
For more information, please have a look at this site.
Related
So, I have struggled with this for a while now and can't figure out what I'm missing. I have a table that holds an entity called Skill and the DataModel looks like this:
public class SkillModel
{
public SkillModel()
{
}
public SkillModel(int skillId)
{
SkillId = skillId;
}
public int SkillId { get; set; } = 0;
public string Name { get; set; } = "";
public Guid DescriptionId { get; set; } = new();
public int SkillGroupId { get; set; } = 0;
public SkillGroupModel SkillGroup { get; set; } = new();
}
It references the SkillGroup which is it's own table and it looks like this:
public class SkillGroupModel
{
public SkillGroupModel()
{
}
public SkillGroupModel(int skillGroupId)
{
SkillGroupId = skillGroupId;
}
public int SkillGroupId { get; set; } = 0;
public string Name { get; set; } = "";
public Guid DescriptionId { get; set; } = new();
public List<SkillModel> Skills { get; set; } = new();
}
They each have their own configuration files and the look like this:
SkillModel
public class SkillConfiguration : IEntityTypeConfiguration<SkillModel>
{
public void Configure(EntityTypeBuilder<SkillModel> builder)
{
var dataSeeds = new DataSeeds();
builder.ToTable("Skills", "Skills");
builder.HasKey(k => k.SkillId);
builder.HasOne(s => s.SkillGroup)
.WithMany(s => s.Skills);
builder.HasData(dataSeeds.Skills);
}
}
SkillGroupModel
var dataSeeds = new DataSeeds();
builder.ToTable("SkillGroups", "Skills")
.HasKey(k => k.SkillGroupId);
builder.HasData(dataSeeds.SkillGroups);
Data seeds looks like this:
SkillGroupModel Seed
public List<SkillGroupModel> GetSkillGroups()
{
return new List<SkillGroupModel>()
{
new()
{
SkillGroupId = 1, Name = "Artisan", DescriptionId = SkillGroupDescriptions["Artisan"].Id
},
...
}
SkillModel Seeds
return new List<SkillModel>()
{
new()
{
SkillId = 1,
Name = "Aesthetics",
DescriptionId = SkillDescriptions["Aesthetics"].Id,
SkillGroupId = 1
},
...
}
So, I was obviously missing something. Ivan Stoev had a great point of not initializing my navigation properties like that, and that was a great help.
I went about it by not using my navigation properties and only setting the FK Properties. I think in the past I was trying to do both and that was causing issues that took me down this path. I'm not sure what I was doing wrong before but the way the documentation for seeding data on MSDN worked fine for me after going back and trying it again.
I have two entities with many to many relationship, I'm saving one entity that has a list of entities, but they get duplicated
ex:
class GENEntity
{
int Id { get; set; }
string Name { get; set; }
List<GENEntityTab> tabs { get; set; }
}
class GENEntityTab
{
int Id { get; set; }
string Name { get; set; }
List<GENEntity> entities { get; set; }
}
When I save object of GENEntity but as a view model as you'll see next, with a list of two GENEntityTab, those two tabs get inserted (duplicated) in the DB. I'm using Web API and angular
In the Repository, it's only called when I click submit (there are some extra properties):
public static JsonViewData AddOrUpdate(ModelDBContext context, GENEntityViewModel entityVM, string name, string id)
{
try
{
var entity = context.Entities.SingleOrDefault(x => x.Id == entityVM.Id);
if (entity == null)
{
entity = new GENEntity();
entity.InjectFrom(entityVM);
context.Entities.Add(entity);
}
else
{
entity.DateUpdated = DateTime.Now;
entity.InjectFrom(entityVM);
}
entity.CreatedById = new Guid(id);
entity.LastUpdatedById = new Guid(id);
context.SaveChanges();
return new JsonViewData { IsSuccess = true, Message = "Created Successfully" };
}
catch (Exception ex)
{
return new JsonViewData { IsSuccess = false, Message = ex.Message };
}
}
the view models:
public class GENEntityTabViewModel
{
public long Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public List<GENEntity> Entities { get; set; } = new List<GENEntity>();
public GENEntityTabViewModel()
{
}
public GENEntityTabViewModel(GENEntityTab entityTab)
{
Id = entityTab.Id;
Name = entityTab.Name;
Description = entityTab.Description;
Entities = entityTab.Entities;
}
}
I get an error when I try to insert a value in my Table.
_dltype is an object of type BRIDownloadType.
using (var db = new BRIDatabase())
{
foreach (var client in db.BRIClients)
{
var todo = new BRIToDo
{
BRIClient = client,
BRIDownloadType = _dltype,
};
db.BRIToDos.Add(todo);
}
db.SaveChanges();
}
Now I get the error:
An Explict Value can't be inserted in the Idendity Column in the BRIDownloadTypes-Table when IDENTITY_INSERT is OFF.
My 2 Tables are
BRIDownloadType
public class BRIDownloadType
{
[Key]
public int DlTypeId { get; set; }
[Required]
[StringLength(15)]
public string DlType { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public virtual ICollection<BRIToDo> BRIToDo { get; set; }
}
BRITodo
public class BRIToDo
{
[Key]
public int ToDoId { get; set; }
[ForeignKey("BRIClient")]
public int ClientId { get; set; }
[ForeignKey("BRITask")]
public int TaskId { get; set; }
public virtual BRIClient BRIClient { get; set; }
public virtual BRITask BRITask { get; set; }
[ForeignKey("BRIDownloadType")]
public int DlTypeId { get; set; }
public virtual BRIDownloadType BRIDownloadType { get; set; }**
}
The interesting thing is, if I do something with my _dltype object, I can use it.
The following code is working and I don't understand why, I'm inserting the exact same object.
using (var db = new BRIDatabase())
{
var dl = db.BRIDownloadTypes.FirstOrDefault(c => c.DlTypeId == _dltype.DlTypeId);
foreach (var client in db.BRIClients)
{
var todo = new BRIToDo
{
BRIClient = client,
BRIDownloadType = _dltype,
};
db.BRIToDos.Add(todo);
}
db.SaveChanges();
}
Can anybody explain to me, why the last approach is working and the first is throwing that error? I just added the line
var dl = db.BRIDownloadTypes.FirstOrDefault(c => c.DlTypeId == _dltype.DlTypeId)
But I'm still inserting the same object. If I insert the Id of the object instead of the object it is also working fine. I have no idea whats going on there.
Am Trying to Print out a student Identity Card using crystal report but all what i could get was this error popping up The data source object is invalid.
Guys please help me to check on this code if am making any mistake...
this is the model
public class CardModel
{
// Properties
public string Department { get; set; }
public string ExpiryDate { get; set; }
public string FirstName { get; set; }
public Sex Gender { get; set; }
public Guid Id { get; set; }
public string MiddleName { get; set; }
public string RegistrationNo { get; set; }
public byte[] SecuritySign { get; set; }
public byte[] StudentPhoto { get; set; }
public string Surname { get; set; }
}
public static class CardModelExtention
{
public static CardModel ToCardModel(this Student identity)
{
return new CardModel
{
Id = identity.Id,
FirstName = identity.FirstName,
MiddleName = identity.MiddleName,
Surname = identity.Surname,
StudentPhoto = identity.Photo.RawPhoto,
SecuritySign = identity.SecuritySignature.RawSignature,
Gender = identity.Sex,
ExpiryDate = identity.ExpiryDate,
Department = identity.Department.DepartmentName,
RegistrationNo = identity.RegistrationNo
};
}
}
and here is the service am using to pull the information from database
public class StudentService : IStudentService
{
ERMUoW _ow;
public StudentService()
{
_ow = new ERMUoW();
}
public CardModel GetStudentById(Guid id)
{
CardModel obj3 = new CardModel();
Student student = _ow.Students.GetAllIncluding(new Expression<Func<Student, object>>[] { st => st.Photo, st => st.Signature, st => st.SecuritySignature, st => st.Department }).Where(x => x.Id == id).SingleOrDefault();
var cardInfo = student.ToCardModel();
return cardInfo;
}
}
public interface IStudentService
{
CardModel GetStudentById(Guid id);
}
This is it and everything around here is working fine and am getting the data very well but when I send it to the method in my contrller that generate the identity card I get that error message
this is the code that generate the card using crytal report
public ActionResult PrintCard(Guid id)
{
var student = _studentCardService.GetStudentById(id);
ReportDocument read = new ReportDocument();
read.Load(Server.MapPath("~/Reports/rpt_StudentCard.rpt"));
read.SetDataSource(student);
Response.Buffer = false;
Response.ClearContent();
Response.ClearHeaders();
try
{
Stream stream = read.ExportToStream(CrystalDecisions.Shared.ExportFormatType.PortableDocFormat);
stream.Seek(0, SeekOrigin.Begin);
return File(stream, "application/pdf", "StudentIdentityCard.pdf");
}
catch (Exception ex)
{
throw ex;
}
}
I will really Appreciate your help thank you...
The data source have to be a List of elements... not a single element.
My movie model has a virtual declaration of ICollection which is working fine in all cases except one. When I try to edit the list from a session variable and call _db.SaveChanges, it is not getting updated to db. Let me tell that I also have a ProducerId field (along with virtual Producer) which is getting updated properly. Where is it going wrong?
Movie model - Movie.cs
public class Movie
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int MovieId { get; set; }
public string Name { get; set; }
public string YearOfRelease { get; set; }
public string Plot { get; set; }
public string ImgUrl { get; set; }
[ForeignKey("Producer")]
public int ProducerId { get; set; }
public virtual Producer Producer { get; set; }
public virtual ICollection<Actor> Actors { get; set; }
public virtual ICollection<MovieReview> Reviews { get; set; }
}
CreateOrEdit Controller -
public ActionResult CreateOrEdit([Bind(Include = "MovieId, Name, Plot, YearOfRelease")]Movie movie)
{
if (ModelState.IsValid)
{
int session_producerId = (int)Session["ProducerId"];
movie.Producer = _db.Producers.Find(session_producerId);
movie.ProducerId = session_producerId;
List<int> actors = Session["Actors"] as List<int>;
ICollection<Actor> session_actors = _db.Actors.Where(a => actors.Contains(a.ActorId)).ToList<Actor>();
movie.Actors = session_actors;
// add the movie to db before saving if creating new
if (movie.MovieId == 0)
_db.Movies.Add(movie);
else
// else trigger the edits
_db.Entry(movie).State = EntityState.Modified;
// finally save changes
_db.SaveChanges();
// removing session information
Session.Clear();
return RedirectToAction("Index");
}
return View(movie);
}
Can someone help me to understand why does _db.Entry(movie).State = EntityState.Modified not updating the Actors collection? Any kind of help is appreciated.
Try this,
if (ModelState.IsValid)
{
Movie movieDb;
if (movie.MovieId == 0)
{
movieDb = new Movie { Actors = new List<Actor>() };
_db.Movies.Add(movieDb);
}
else
{
movieDb = _db.Movies.Include(m => m.Actors).FirstOrDefault(m => m.MovieId == movie.MovieId);
}
// Sets producer.
int session_producerId = (int)Session["ProducerId"];
movieDb.Producer = _db.Producers.Find(session_producerId);
movieDb.ProducerId = session_producerId;
// Sets actors.
List<int> actors = Session["Actors"] as List<int>;
ICollection<Actor> session_actors = _db.Actors.Where(a => actors.Contains(a.ActorId)).ToList<Actor>();
Array.ForEach(session_actors.ToArray(), actor => movieDb.Actors.Add(actor));
// Sets other movie's properties here..
// finally save changes
_db.SaveChanges();
// removing session information
Session.Clear();
return RedirectToAction("Index");
}