Entity Framework validation failed when updating a class with virtual properties - entity-framework

I'm having problems updating a property of a class when the class contains virtual properties. Here is my code
public class Policy
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long id { get; set; }
[UIHint("Company"), Required]
public virtual Company company { get; set; }
[UIHint("Productor"), Required]
public virtual Productor productor { get; set; }
[MaxLength(1000)]
public string comments { get; set; }
}
public class Company
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long id { get; set; }
[MaxLength(100)]
public string name { get; set; }
}
//Then Productor class is the same as company but with another name
public static int updateComment(long id, string comments)
{
MemberPolicies mp = new MemberPolicies();
Policy p = mp.Policies.Single(o => o.id == id);
p.comments = comments;
int afectedRecords = -1;
try
{
afectedRecords = mp.SaveChanges();
}
catch (DbEntityValidationException dbEx)
{
foreach (var validationErrors in dbEx.EntityValidationErrors)
{
foreach (var validationError in validationErrors.ValidationErrors)
{
Trace.TraceInformation("Property: {0} Error: {1}", validationError.PropertyName, validationError.ErrorMessage);
}
}
}
return afectedRecords;
}
The properties which are causing the validation errors are company and company, but I only want to update the property comment.
Some help is appreciate.
Thanks

EF does not lazy load your virtual properties when you try to save your entity (damn it).
You can do either of the following.
Use Include:
Policy p = mp.Policies
.Include(p => p.company)
.Include(p => p.productor).Single(o => o.id == id);
p.comments = comments;
Or use Load:
Policy p = mp.Policies.Single(o => o.id == id);
p.comments = comments;
mp.Entry(p).Reference(p => p.company).Load();
mp.Entry(p).Reference(p => p.productor).Load();
Or better you can write more elegant code as explained here.

Related

Entity Framework Core - issue with loading related entities that also contain related entities

I am using Entity Framework Core following Chris Sakell's blog here.
He uses generics to manage his repositories and also a base repository that he uses for all the other repositories.
Part of the base repository has the the following code for the retrieval of a single entity that also downloads related entities using the includeProperties option. Here is the generic code for a retrieving a single item.
public T GetSingle(Expression<Func<T, bool>> predicate, params Expression<Func<T, object>>[] includeProperties)
{
IQueryable<T> query = _context.Set<T>();
foreach (var includeProperty in includeProperties)
{
query = query.Include(includeProperty);
}
return query.Where(predicate).FirstOrDefault();
}
I am using it on a client table that has many jobs attached to it.
This is how I structured my code.
public ClientDetailsViewModel GetClientDetails(int id)
{
Client _client = _clientRepository
.GetSingle(c => c.Id == id, c => c.Creator, c => c.Jobs, c => c.State);
if(_client != null)
{
ClientDetailsViewModel _clientDetailsVM = mapClientDetailsToVM(_client);
return _clientDetailsVM;
}
else
{
return null;
}
}
The line:
.GetSingle(c => c.Id == id, c => c.Creator, c => c.Jobs, c => c.State);
successfully retrieves values for creator state and job.
However, nothing is retrieved for those related entities associated with the "jobs".
In particuar, JobVisits is a collection of visits to jobs.
For completeness I am adding the "job" and "jobvisit" entities below
public class Job : IEntityBase
{
public int Id { get; set; }
public int? ClientId { get; set; }
public Client Client { get; set; }
public int? JobVisitId { get; set; }
public ICollection<JobVisit> JobVisits { get; set; }
public int? JobTypeId { get; set; }
public JobType JobType { get; set; }
public int? WarrantyStatusId { get; set; }
public WarrantyStatus WarrantyStatus { get; set; }
public int? StatusId { get; set; }
public Status Status { get; set; }
public int? BrandId { get; set; }
public Brand Brand { get; set; }
public int CreatorId { get; set; }
public User Creator { get; set; }
....
}
public class JobVisit : IEntityBase
{
...
public int? JobId { get; set; }
public Job Job { get; set; }
public int? JobVisitTypeId { get; set; }
public JobVisitType VisitType { get; set; }
}
My question is, how do I modify the repository code above and my GetSingle use so that I can also load the related enitities JobVisit collection and the other related single entities Brand and JobType?
It is intended that navigation properties are not necessary retrieved for associated with the "jobs". That is why some properties are null. By default the .Include(property); goes only 1-level deep and that is a good thing. It prevents your query from fetching all the data of your database.
If you want to include multiple levels, you should use .ThenInclude(property) after .Include(property). From the documentation:
using (var context = new BloggingContext())
{
var blogs = context.Blogs
.Include(blog => blog.Posts)
.ThenInclude(post => post.Author)
.ToList();
}
My advice is that your method public T GetSingle(...) is nice and I would not change it in order to include deeper levels. Instead of that, you can simply use explicit loading. From the documentation:
using (var context = new BloggingContext())
{
var blog = context.Blogs
.Single(b => b.BlogId == 1);
context.Entry(blog)
.Collection(b => b.Posts)
.Load();
context.Entry(blog)
.Reference(b => b.Owner)
.Load();
}

EF6: Single relationship table for multiple related entities

I have a EF Model with many entities, like Nodes, Attributes, Tags, etc.
There is also an "Alias" entity, and pretty much every other entity else can have a many-to-many relationship with Aliases. One of the undesired things about this is the number of tables that are created to track these relationships (eg. NodeAlias, AttributeAlias, etc.).
Are there any design alternatives that could map an Alias to all of the other entities in a single table? I was thinking maybe something along these lines if it's possible:
+---------+--------+-------------+-----------+
| AliasId | NodeId | AttributeId | TagId |
+---------+--------+-------------+-----------+
| 1 | 1 | 2 | 3 |
+---------+--------+-------------+-----------+
I updated my solution to provide many-to-many relationships between aliases and every other entity.
I intentionally posted this as a separate answer so that my previous answer can also remain here if anyone would need it.
Step #1: I created extension methods for getting and setting property values using reflection in a convenient way:
public static class ObjectExtensions
{
public static TResult GetPropertyValue<TResult>(this object entity, string propertyName)
{
object propertyValue = entity?.GetType().GetProperty(propertyName)?.GetValue(entity);
try
{
return (TResult)propertyValue;
}
catch
{
return default(TResult);
}
}
public static void SetPropertyValue(this object entity, string propertyName, object value)
{
entity?.GetType().GetProperty(propertyName)?.SetValue(entity, value);
}
}
Step #2: I updated the models to provide many-to-many relationship.
public class Node
{
[Key]
public int NodeId { get; set; }
public string Name { get; set; }
public virtual ICollection<AliasMapping> AliasMappings { get; set; }
}
public class Attribute
{
[Key]
public int AttributeId { get; set; }
public string Name { get; set; }
public virtual ICollection<AliasMapping> AliasMappings { get; set; }
}
public class Tag
{
[Key]
public int TagId { get; set; }
public string Name { get; set; }
public virtual ICollection<AliasMapping> AliasMappings { get; set; }
}
public class Alias
{
[Key]
public int AliasId { get; set; }
public string Name { get; set; }
public virtual ICollection<AliasMapping> AliasMappings { get; set; }
}
public class AliasMapping
{
[Key]
public int Id { get; set; }
[ForeignKey("Alias")]
public int AliasId { get; set; }
public Alias Alias { get; set; }
[ForeignKey("Node")]
public int? NodeId { get; set; }
public virtual Node Node { get; set; }
[ForeignKey("Attribute")]
public int? AttributeId { get; set; }
public virtual Attribute Attribute { get; set; }
[ForeignKey("Tag")]
public int? TagId { get; set; }
public virtual Tag Tag { get; set; }
}
Step #3: Due to relationship changes the MyDbContext could have been simplified as the [ForeignKey] data annotations are enough.
public class MyDbContext : DbContext
{
public DbSet<Node> Nodes { get; set; }
public DbSet<Attribute> Attributes { get; set; }
public DbSet<Tag> Tags { get; set; }
public DbSet<Alias> Aliases { get; set; }
public DbSet<AliasMapping> AliasMappings { get; set; }
}
Step #4: I also updated the extension methods so that you can create and remove alias mappings.
public static class AliasExtensions
{
public static void CreateMapping(this MyDbContext context, object entity, Alias alias)
{
if (entity == null || alias == null)
{
return;
}
string mappingEntityPropertyName = entity.GetType().Name;
string entityKeyPropertyName = String.Concat(mappingEntityPropertyName, "Id");
int entityId = entity.GetPropertyValue<int>(entityKeyPropertyName);
AliasMapping[] mappings =
context
.AliasMappings
.Where(mapping => mapping.AliasId == alias.AliasId)
.ToArray();
if (mappings.Any(mapping => mapping.GetPropertyValue<int?>(entityKeyPropertyName) == entityId))
{
// We already have the mapping between the specified entity and alias.
return;
}
bool usableMappingExists = true;
var usableMapping = mappings.FirstOrDefault(mapping => mapping.GetPropertyValue<int?>(entityKeyPropertyName) == null);
if (usableMapping == null)
{
usableMappingExists = false;
usableMapping = new AliasMapping()
{
Alias = alias
};
}
usableMapping.SetPropertyValue(mappingEntityPropertyName, entity);
usableMapping.SetPropertyValue(entityKeyPropertyName, entityId);
if (!usableMappingExists)
{
context.AliasMappings.Add(usableMapping);
}
// This step is required here, I think due to using reflection.
context.SaveChanges();
}
public static void RemoveMapping(this MyDbContext context, object entity, Alias alias)
{
if (entity == null || alias == null)
{
return;
}
string mappingEntityPropertyName = entity.GetType().Name;
string entityKeyPropertyName = String.Concat(mappingEntityPropertyName, "Id");
int entityId = entity.GetPropertyValue<int>(entityKeyPropertyName);
AliasMapping[] mappings =
context
.AliasMappings
.Where(mapping => mapping.AliasId == alias.AliasId)
.ToArray();
AliasMapping currentMapping = mappings.FirstOrDefault(mapping => mapping.GetPropertyValue<int?>(entityKeyPropertyName) == entityId);
if (currentMapping == null)
{
// There is no mapping between the specified entity and alias.
return;
}
currentMapping.SetPropertyValue(mappingEntityPropertyName, null);
currentMapping.SetPropertyValue(entityKeyPropertyName, null);
// This step is required here, I think due to using reflection.
context.SaveChanges();
}
}
Step #5: Updated the console app steps to align it with the changes.
class Program
{
static void Main(string[] args)
{
// Consider specify the appropriate database initializer!
// I use DropCreateDatabaseAlways<> strategy only for this example.
Database.SetInitializer(new DropCreateDatabaseAlways<MyDbContext>());
var aliases =
Enumerable
.Range(1, 9)
.Select(index => new Alias() { Name = String.Format("Alias{0:00}", index) })
.ToList();
var attributes =
Enumerable
.Range(1, 5)
.Select(index => new Attribute() { Name = String.Format("Attribute{0:00}", index) })
.ToList();
var nodes =
Enumerable
.Range(1, 5)
.Select(index => new Node() { Name = String.Format("Node{0:00}", index) })
.ToList();
var tags =
Enumerable
.Range(1, 5)
.Select(index => new Tag() { Name = String.Format("Tag{0:00}", index) })
.ToList();
using (var context = new MyDbContext())
{
context.Aliases.AddRange(aliases);
context.Nodes.AddRange(nodes);
context.Attributes.AddRange(attributes);
context.Tags.AddRange(tags);
// Always save changes after adding an entity but before trying to create a mapping.
context.SaveChanges();
// One Alias To Many Entities
context.CreateMapping(nodes[0], aliases[0]);
context.CreateMapping(nodes[1], aliases[0]);
context.CreateMapping(nodes[2], aliases[0]);
context.CreateMapping(nodes[3], aliases[0]);
context.CreateMapping(attributes[0], aliases[0]);
context.CreateMapping(attributes[1], aliases[0]);
context.CreateMapping(attributes[2], aliases[0]);
context.CreateMapping(tags[0], aliases[0]);
context.CreateMapping(tags[1], aliases[0]);
// One Entity To Many Aliases
context.CreateMapping(nodes[4], aliases[0]);
context.CreateMapping(nodes[4], aliases[1]);
context.CreateMapping(nodes[4], aliases[2]);
context.CreateMapping(attributes[3], aliases[1]);
context.CreateMapping(attributes[3], aliases[3]);
context.CreateMapping(tags[2], aliases[2]);
context.CreateMapping(tags[2], aliases[3]);
// Remove mapping
context.RemoveMapping(nodes[4], aliases[0]);
// Not really needed here as both 'CreateMapping' and 'RemoveMapping' save the changes
context.SaveChanges();
}
Console.Write("Press any key to continue . . .");
Console.ReadKey(true);
}
}
Please note: RemoveMapping() will not delete an AliasMapping even if no entity is associated with it! But CreateMapping() will make use of it later if needed. E.g. look at the screenshot below and check AliasMapping where Id = 5.
Screenshot about the execution result:
You were talking about many-to-many relationship but reading your post I think it is more likely a "special one-to-many" relationship, actually "combined multiple one-to-one" relationship as I see that an Alias can be mapped to a single Node AND/OR to a single Attribute AND/OR to a single Tag.
I think I found a solution for this case.
If it's not the case and an Alias can be mapped to multiple Node AND/OR to multiple Attribute AND/OR to multiple Tag then I think this solution below needs only a small change. :)
Step #1 - These are my example models
public class Node
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public virtual AliasMapping AliasMapping { get; set; }
}
public class Attribute
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public virtual AliasMapping AliasMapping { get; set; }
}
public class Tag
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public virtual AliasMapping AliasMapping { get; set; }
}
public class Alias
{
[Key]
public int AliasId { get; set; }
public string Name { get; set; }
public virtual AliasMapping AliasMapping { get; set; }
}
Step #2 - Creating the custom mapping table
public class AliasMapping
{
[Key]
[ForeignKey("Alias")]
public int AliasId { get; set; }
public Alias Alias { get; set; }
[ForeignKey("Node")]
public int NodeId { get; set; }
public virtual Node Node { get; set; }
[ForeignKey("Attribute")]
public int AttributeId { get; set; }
public virtual Attribute Attribute { get; set; }
[ForeignKey("Tag")]
public int TagId { get; set; }
public virtual Tag Tag { get; set; }
}
Step #3 - Creating the DbContext
public class MyDbContext : DbContext
{
public DbSet<Node> Nodes { get; set; }
public DbSet<Attribute> Attributes { get; set; }
public DbSet<Tag> Tags { get; set; }
public DbSet<Alias> Aliases { get; set; }
public DbSet<AliasMapping> AliasMappings { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder
.Entity<AliasMapping>()
.HasOptional(mapping => mapping.Attribute)
.WithOptionalPrincipal(attribute => attribute.AliasMapping)
.Map(config => config.MapKey("AliasId"));
modelBuilder
.Entity<AliasMapping>()
.HasOptional(mapping => mapping.Node)
.WithOptionalPrincipal(node => node.AliasMapping)
.Map(config => config.MapKey("AliasId"));
modelBuilder
.Entity<AliasMapping>()
.HasOptional(mapping => mapping.Tag)
.WithOptionalPrincipal(tag => tag.AliasMapping)
.Map(config => config.MapKey("AliasId"));
}
}
Step #4 - Creating extension method so that creating a relationship will be easy
public static class AliasExtensions
{
public static void CreateMapping<TEntity>(this MyDbContext context, TEntity entity, Alias alias)
{
string mappingEntityPropertyName = typeof(TEntity).Name;
string entityKeyPropertyName = String.Concat(mappingEntityPropertyName, "Id");
bool entityExists = true;
var mapping = context.AliasMappings.Find(alias.AliasId);
if (mapping == null)
{
entityExists = false;
mapping = new AliasMapping()
{
Alias = alias
};
}
typeof(AliasMapping)
.GetProperty(mappingEntityPropertyName)
.SetValue(mapping, entity);
typeof(AliasMapping)
.GetProperty(entityKeyPropertyName)
.SetValue(mapping, typeof(TEntity).GetProperty("Id").GetValue(entity));
if (!entityExists)
{
context.AliasMappings.Add(mapping);
}
}
}
Step #5 - Created a console app to see this working
class Program
{
static readonly Random rnd = new Random(DateTime.Now.TimeOfDay.Milliseconds);
static void Main(string[] args)
{
Database.SetInitializer(new DropCreateDatabaseAlways<MyDbContext>());
var aliases =
Enumerable
.Range(1, 9)
.Select(index => new Alias() { Name = String.Format("Alias{0:00}", index) })
.ToList();
var attributes =
Enumerable
.Range(1, 5)
.Select(index => new Attribute() { Name = String.Format("Attribute{0:00}", index) })
.ToList();
var nodes =
Enumerable
.Range(1, 5)
.Select(index => new Node() { Name = String.Format("Node{0:00}", index) })
.ToList();
var tags =
Enumerable
.Range(1, 5)
.Select(index => new Tag() { Name = String.Format("Tag{0:00}", index) })
.ToList();
using (var context = new MyDbContext())
{
context.Aliases.AddRange(aliases);
context.Nodes.AddRange(nodes);
context.Attributes.AddRange(attributes);
context.Tags.AddRange(tags);
context.SaveChanges();
// Associate aliases to attributes
attributes.ForEach(attribute =>
{
var usableAliases = aliases.Where(alias => alias.AliasMapping?.Attribute == null).ToList();
var selectedAlias = usableAliases[rnd.Next(usableAliases.Count)];
context.CreateMapping(attribute, selectedAlias);
});
// Associate aliases to nodes
nodes.ForEach(node =>
{
var usableAliases = aliases.Where(alias => alias.AliasMapping?.Node == null).ToList();
var selectedAlias = usableAliases[rnd.Next(usableAliases.Count)];
context.CreateMapping(node, selectedAlias);
});
// Associate aliases to tags
tags.ForEach(tag =>
{
var usableAliases = aliases.Where(alias => alias.AliasMapping?.Tag == null).ToList();
var selectedAlias = usableAliases[rnd.Next(usableAliases.Count)];
context.CreateMapping(tag, selectedAlias);
});
context.SaveChanges();
}
Console.Write("Press any key to continue . . .");
Console.ReadKey(true);
}
}

Only initializers, entity members, and entity navigation properties are supported when including child table in query

I've got a simple one-to-many db relationship defined by EF 6.1. The database that is generated appears correct and has the appropriate explicit relationship. However, when I try a query involving the child table, I get a NotSupportedException (see title of post). The failing code is in GetContractList (see below).
I did some digging and found some people having this problem, but those issues seemed related to attempting to include non-entity items in queries; I don't think that's what's happening here.
Anyone see what I'm doing wrong?
[Table("Contract")]
public class Contract : IContract
{
[Key,
DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ContractId { get; set; }
[StringLength(1023),
Required]
public string Name { get; set; }
public DateTime DateBegin { get; set; }
public DateTime DateEnd { get; set; }
public string PhoneNumber { get; set; }
public virtual ICollection<IMarket> Markets { get; set; }
public Contract()
{
Markets = new List<IMarket>();
}
}
[Table("Market")]
public class Market : IMarket
{
[Key, Column(Order = 0)]
public int ContractId { get; set; }
[Key, Column(Order = 1)]
public int MarketId { get; set; }
}
public IEnumerable<IIdName> GetContractList(IAffiliateContractSearchCriteria criteria)
{
var now = DateTime.UtcNow;
// This is the line throwing the exception.
return _repository.AsQueryable().Where(c => (criteria.IncludeOnlyActive
? c.DateBegin < now
&& (c.DateEnd > now || c.DateEnd <= SqlDateTime.MinValue.Value)
: true)
&& (c.Markets.Any()
? c.Markets.Select(m => m.MarketId).Any(x => criteria.MarketIds.Contains(x))
: true)).OrderBy(a => a.Name).Select(a => new IdName() { Id = a.AffiliateContractId, Name = a.Name });
}
public class ContractSearchCriteria : IContractSearchCriteria
{
public bool IncludeOnlyActive { get; set; }
public List<int> MarketIds { get; set; }
public ContractSearchCriteria()
{
IncludeOnlyActive = false;
MarketIds = new List<int>();
}
public ContractSearchCriteria(bool includeOnlyActive, int[] marketIds)
: this()
{
IncludeOnlyActive = includeOnlyActive;
MarketIds.AddRange(marketIds);
}
}
Ok, so the problem is that you can't use interfaces when defining EF Entity objects and relationships.
My bad.

I have a little complex model, about EF cascade delete

Code first,and model look like this Figure:
public class Post
{
public int Id { get; set; }
public string title { get; set; }
public int fid { get; set; }
public int uid { get; set; }
public virtual lnk lnk { get; set; }
public virtual user user { get; set; }
public virtual ICollection<img> imgs { get; set; }
}
public class lnk
{
public int Id { get; set; }
public string lnkman { get; set; }
}
public class model1:Post
{
public string content { get; set; }
}
public class user
{
public int uid { get;set; }
public string Name {get;set;}
public virtual ICollection<Post> posts { get; set; }
}
public class img
{
public int id { get; set; }
public string imgUrl { get; set; }
public int pid { get; set; }
public virtual Post post { get; set; }
}
public class PostContext : DbContext
{
public DbSet<Post> posts { get; set; }
public DbSet<user> users { get; set; }
public DbSet<img> imgs { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Post>().HasKey(i=>i.Id);
modelBuilder.Entity<lnk>().HasKey(i => i.Id);
modelBuilder.Entity<Post>().HasRequired(i => i.lnk).WithRequiredPrincipal();
modelBuilder.Entity<Post>().ToTable("Post");
modelBuilder.Entity<lnk>().ToTable("Post");
modelBuilder.Entity<model1>().ToTable("model1");
modelBuilder.Entity<user>().HasKey(i => i.uid).ToTable("user");
modelBuilder.Entity<user>().HasMany(i => i.posts)
.WithRequired(i => i.user)
.HasForeignKey(i =>i.uid).WillCascadeOnDelete(false);
modelBuilder.Entity<img>().HasKey(i => i.id).ToTable("img");
modelBuilder.Entity<Post>().HasMany(i => i.imgs)
.WithRequired(i => i.post)
.HasForeignKey(i => i.pid)
.WillCascadeOnDelete(false);
}
}
the add and list method work well. but delele like this :
using (var db = new PostContext())
{
//model1 ToDel = new model1{ Id=id };
//b.Entry(ToDel).State = System.Data.EntityState.Deleted;
var ToDel = db.users.Single(i => i.uid == id);
db.users.Remove(ToDel);
db.SaveChanges();
return id + "DE: well done!" ;
}
System.Data.SqlClient.SqlException: DELETE statement with REFERENCE constraint "FK_dbo.Post_dbo.user_uid" conflict。The conflict occurred in database "jefunfl",table "dbo.Post", column 'uid'
I want delete users with user's posts, and posts with post's imgs.
How can I do this? Can some one give any tips?
btw,my Chinese English may be funny. forgive me.
this is my first post,but I am not new guy. thanks
bypass all one day, i found it. and thank you for answer to me.
1:
i delete WillCascadeOnDelete() method,so let ef do it by Conventions.
2:
i found that ,when i delete the posts,i can't directly delete with this statement like ToDel = db.posts.Single(i => i.Id == id)
but this:
ToDel = db.posts.Include(i=>i.lnk).Single(i => i.Id == id)
post and lnk has a relatialship that is called table splitting. both mapping to the same post table. when delete posts, EF can auto delete post.imgs by conventions. what will happen when delete user ? and so i got it. my code blow:
var ToDel = db.users.Include(i => i.posts)
.Single(i => i.uid == id);
var PostToDel = db.posts.Include(i => i.lnk)
.Single(i => i.Id == ToDel.uid);
db.posts.Remove(PostToDel);
db.users.Remove(ToDel);
db.SaveChanges();
this will work well. even not perfectly. but it can do what i want without a exception.
at last,the biggest problem is post class and lnk class has a "table splitting".
when delete post we should explicitly include.
i dont know if you can understand me, my english is very ugly. i am foolish with courage.
thank u. i am happy

why the explicit load does not work,and the navigation property always null?

All my code is here,quite simple,and I don't konw where it goes wrong.
Person and Task has an many-to-many relationship.
I want to load someone's task using the explicit way.
I follow the way this post shows,and i can't make it work.
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Task> Tasks { get; set; }
}
public class Task
{
public int Id { get; set; }
public string Subject { get; set; }
public ICollection<Person> Persons { get; set; }
}
public class Ctx : DbContext
{
public Ctx()
: base("test")
{
this.Configuration.LazyLoadingEnabled = false;
this.Configuration.ProxyCreationEnabled = false;
}
public DbSet<Person> Persons { get; set; }
public DbSet<Task> Task { get; set; }
}
class Program
{
static void Main(string[] args)
{
//add some data as follows
//using (var ctx = new Ctx())
//{
//ctx.Persons.Add(new Person { Name = "haha" });
//ctx.Persons.Add(new Person { Name = "eeee" });
//ctx.Task.Add(new Task { Subject = "t1" });
//ctx.Task.Add(new Task { Subject = "t2" });
//ctx.SaveChanges();
//var p11 = ctx.Persons.FirstOrDefault();
//ctx.Task.Include(p2 => p2.Persons).FirstOrDefault().Persons.Add(p11);
//ctx.SaveChanges();
//}
var context = new Ctx();
var p = context.Persons.FirstOrDefault();
context.Entry(p)
.Collection(p1 => p1.Tasks)
.Query()
//.Where(t => t.Subject.StartsWith("t"))
.Load();
//the tasks should have been loaded,isn't it?but no...
Console.WriteLine(p.Tasks != null);//False
Console.Read();
}
}
Is there anything wrong with my code?I'm really new to EF,so please, someone help me.
The problem is your .Query() call. Instead of loading the collection, you are getting a copy of the IQueryable that would be used to load, then executing the query.
Remove your .Query() line and it will work.
If what you are looking for is getting a filtered list of collection elements, you can do this:
var filteredTasks = context.Entry(p)
.Collection(p1 => p1.Tasks)
.Query()
.Where(t => t.Subject.StartsWith("t"))
.ToList();
This will not set p.Tasks, nor is it a good idea to do so, because you'd be corrupting the domain model.
If you really, really want to do that... this might do the trick (untested):
var collectionEntry = context.Entry(p).Collection(p1 => p1.Tasks);
collectionEntry.CurrentValue =
collectionEntry.Query()
.Where(t => t.Subject.StartsWith("t"))
.ToList();
This solution worked for me :
For some reasons EF requires virtual keyword on navigation property, so the entities should be like this :
public class Person
{
//...
public virtual ICollection<Task> Tasks { get; set; }
}
public class Task
{
//...
public virtual ICollection<Person> Persons { get; set; }
}