n:m with payload in Entity Framework 4.x
up vote
2
down vote
favorite
I am trying to create the following relation in EF4.x
"one Material is made of many Materials to a certain amount and each Material can be used in Materials"
ideally that would convert to Material n:m Material but the Content is a payload, so I have translated that to:
"Material 1:n MaterialUsage" and "MaterialUsage m:1 Material"
I have created two tables since I have a payload (certain amount)
'Material' and 'MaterialUsage'
in Material I have defined a relation 'IsMadeOf' which links to 'MaterialUsage.IsUsedIn' and
a relation 'IsUsedFor' which links to 'MaterialUsage.IsMadeOf'
in MaterialUsage I have aside of the two above described the filed 'Content'.
Now to my problem:
If I delete a Material I run into an error message essentially saying that within the Association 'MaterialMaterialUsage', which identifies the relation "Material.IsUsedFor <-> MaterialUsage.IsMadeOf", a relation is in status 'Deleted' and due to the multiplicity definition a corresponding 'MaterialUsage' record must be in status 'Deleted' as well, which was not found.
My intention is however to delete the material and all 'MaterialUsages' which are identified thru 'Material.IsMadeOf'. Which does not include the materials referenced but only the 'MaterialUsage' records that have a reference in 'IsUsedIN' to the Material to be deleted.
Now I am trying to find a clear way to do so. I am guessing it could work with referential integrity but I am not too familiar with that and therefor I am lost.
I can change the DB-Design no problem.
entity-framework database-design c#-4.0
add a comment |
up vote
2
down vote
favorite
I am trying to create the following relation in EF4.x
"one Material is made of many Materials to a certain amount and each Material can be used in Materials"
ideally that would convert to Material n:m Material but the Content is a payload, so I have translated that to:
"Material 1:n MaterialUsage" and "MaterialUsage m:1 Material"
I have created two tables since I have a payload (certain amount)
'Material' and 'MaterialUsage'
in Material I have defined a relation 'IsMadeOf' which links to 'MaterialUsage.IsUsedIn' and
a relation 'IsUsedFor' which links to 'MaterialUsage.IsMadeOf'
in MaterialUsage I have aside of the two above described the filed 'Content'.
Now to my problem:
If I delete a Material I run into an error message essentially saying that within the Association 'MaterialMaterialUsage', which identifies the relation "Material.IsUsedFor <-> MaterialUsage.IsMadeOf", a relation is in status 'Deleted' and due to the multiplicity definition a corresponding 'MaterialUsage' record must be in status 'Deleted' as well, which was not found.
My intention is however to delete the material and all 'MaterialUsages' which are identified thru 'Material.IsMadeOf'. Which does not include the materials referenced but only the 'MaterialUsage' records that have a reference in 'IsUsedIN' to the Material to be deleted.
Now I am trying to find a clear way to do so. I am guessing it could work with referential integrity but I am not too familiar with that and therefor I am lost.
I can change the DB-Design no problem.
entity-framework database-design c#-4.0
add a comment |
up vote
2
down vote
favorite
up vote
2
down vote
favorite
I am trying to create the following relation in EF4.x
"one Material is made of many Materials to a certain amount and each Material can be used in Materials"
ideally that would convert to Material n:m Material but the Content is a payload, so I have translated that to:
"Material 1:n MaterialUsage" and "MaterialUsage m:1 Material"
I have created two tables since I have a payload (certain amount)
'Material' and 'MaterialUsage'
in Material I have defined a relation 'IsMadeOf' which links to 'MaterialUsage.IsUsedIn' and
a relation 'IsUsedFor' which links to 'MaterialUsage.IsMadeOf'
in MaterialUsage I have aside of the two above described the filed 'Content'.
Now to my problem:
If I delete a Material I run into an error message essentially saying that within the Association 'MaterialMaterialUsage', which identifies the relation "Material.IsUsedFor <-> MaterialUsage.IsMadeOf", a relation is in status 'Deleted' and due to the multiplicity definition a corresponding 'MaterialUsage' record must be in status 'Deleted' as well, which was not found.
My intention is however to delete the material and all 'MaterialUsages' which are identified thru 'Material.IsMadeOf'. Which does not include the materials referenced but only the 'MaterialUsage' records that have a reference in 'IsUsedIN' to the Material to be deleted.
Now I am trying to find a clear way to do so. I am guessing it could work with referential integrity but I am not too familiar with that and therefor I am lost.
I can change the DB-Design no problem.
entity-framework database-design c#-4.0
I am trying to create the following relation in EF4.x
"one Material is made of many Materials to a certain amount and each Material can be used in Materials"
ideally that would convert to Material n:m Material but the Content is a payload, so I have translated that to:
"Material 1:n MaterialUsage" and "MaterialUsage m:1 Material"
I have created two tables since I have a payload (certain amount)
'Material' and 'MaterialUsage'
in Material I have defined a relation 'IsMadeOf' which links to 'MaterialUsage.IsUsedIn' and
a relation 'IsUsedFor' which links to 'MaterialUsage.IsMadeOf'
in MaterialUsage I have aside of the two above described the filed 'Content'.
Now to my problem:
If I delete a Material I run into an error message essentially saying that within the Association 'MaterialMaterialUsage', which identifies the relation "Material.IsUsedFor <-> MaterialUsage.IsMadeOf", a relation is in status 'Deleted' and due to the multiplicity definition a corresponding 'MaterialUsage' record must be in status 'Deleted' as well, which was not found.
My intention is however to delete the material and all 'MaterialUsages' which are identified thru 'Material.IsMadeOf'. Which does not include the materials referenced but only the 'MaterialUsage' records that have a reference in 'IsUsedIN' to the Material to be deleted.
Now I am trying to find a clear way to do so. I am guessing it could work with referential integrity but I am not too familiar with that and therefor I am lost.
I can change the DB-Design no problem.
entity-framework database-design c#-4.0
entity-framework database-design c#-4.0
edited Nov 19 at 16:30
Cœur
17.2k9102141
17.2k9102141
asked Jul 21 '11 at 10:44
mph
396
396
add a comment |
add a comment |
1 Answer
1
active
oldest
votes
up vote
1
down vote
accepted
I'll try a halfbaked mix of answer and question. In EF 4.1 with DbContext
API I would create the following model classes (I hope I understood your description correctly):
public class Material
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<MaterialUsage> IsMadeOf { get; set; }
public ICollection<MaterialUsage> IsUsedFor { get; set; }
}
public class MaterialUsage
{
public int Id { get; set; }
public int Content { get; set; }
public Material IsUsedIn { get; set; }
public Material IsMadeOf { get; set; }
}
And this derived context and mapping:
public class MyContext : DbContext
{
public DbSet<Material> Materials { get; set; }
public DbSet<MaterialUsage> MaterialUsages { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Material>()
.HasMany(m => m.IsMadeOf)
.WithRequired(m => m.IsUsedIn)
.WillCascadeOnDelete(false);
modelBuilder.Entity<Material>()
.HasMany(m => m.IsUsedFor)
.WithRequired(m => m.IsMadeOf)
.WillCascadeOnDelete(false);
}
}
I've set the navigation properties in MaterialUsage
to Required
because I think that a MaterialUsage
cannot exist without a reference to the materials. Is that right? As far as I can see it is necessary to turn off cascading delete, otherwise EF will complain about multipe possible cascading delete paths which are not allowed.
Now, to create materials and their relationships something like this would work:
using (var context = new MyContext())
{
var copper = new Material { Name = "Copper" };
context.Materials.Add(copper);
var zinc = new Material { Name = "Zinc" };
context.Materials.Add(zinc);
var brass = new Material
{
Name = "Brass",
IsMadeOf = new List<MaterialUsage>
{
new MaterialUsage { Content = 10, IsMadeOf = copper },
new MaterialUsage { Content = 20, IsMadeOf = zinc }
}
};
context.Materials.Add(brass);
context.SaveChanges();
}
The result in the database is:
Table Materials Table MaterialUsages
Id Name Id Content IsUsedIn_Id IsMadeOf_Id
--------- -------------------------------------------
1 Brass 1 10 1 2
2 Copper 2 20 1 3
3 Zinc
Now, deleting is difficult because Material
appears in both relationships. Especially I don't know how you could accomplish this:
My intention is however to delete the material and all
'MaterialUsages' which are identified thru 'Material.IsMadeOf'
If I understand this correctly you would like to do something like this to delete zinc:
var zinc = context.Materials
.Include(m => m.IsMadeOf)
.Where(m => m.Name == "Zinc")
.Single();
foreach (var usage in zinc.IsMadeOf.ToList())
context.MaterialUsages.Remove(usage);
context.Materials.Remove(zinc);
context.SaveChanges();
This doesn't work because Zinc is made of nothing (the IsMadeOf
collection empty, so the loop above does nothing). But if you remove zinc now you violate a constraint namely that zinc is used for brass. (Id = 2 in the MaterialUsages
table cannot exist without zinc.)
I my opinion you must also delete the MaterialUsages
which are identified by Material.IsUsedFor
:
var zinc = context.Materials
.Include(m => m.IsMadeOf)
.Include(m => m.IsUsedFor)
.Where(m => m.Name == "Zinc")
.Single();
foreach (var usage in zinc.IsMadeOf.ToList())
context.MaterialUsages.Remove(usage);
foreach (var usage in zinc.IsUsedFor.ToList())
context.MaterialUsages.Remove(usage);
context.Materials.Remove(zinc);
context.SaveChanges();
This would delete Id = 3 in the Materials
table and also Id = 2 in the MaterialsUsages
table, fullfilling the referential constraints now.
Not sure if this is what you want.
Edit
I believe I see now: You actually want to have the exception which is thrown due to the violated constraint when you would delete zinc. Because: It should not be allowed to delete a material as long as it is used in another material (zinc is used in brass, therefore it's forbidden to delete zinc as long as brass is in the database). OK, then replacing zinc by brass in the example would work indeed:
var brass = context.Materials
.Include(m => m.IsMadeOf)
.Where(m => m.Name == "Brass")
.Single();
foreach (var usage in brass.IsMadeOf.ToList())
context.MaterialUsages.Remove(usage);
context.Materials.Remove(brass);
context.SaveChanges();
It just deletes both rows in the MaterialUsages
table and brass in the Material
table.
Edit 2
If you want to check if the material to delete is used for any other material you could test this before you actually try to delete:
if (context.Materials
.Where(m => m.Name == "Brass")
.Select(m => !m.IsUsedFor.Any())
.Single())
{
// the code snippet above
}
else
{
// "Brass" cannot be deleted since it is used for other materials...
}
Thank you for the detailed answer.
– mph
Jul 25 '11 at 7:52
Sorry - got tricked by the system. Thank you for the detailed answer. I am very happy that you confirm that the cascading delete is violating EF rules. And I have also been working on a delete that complies to the referential constrains. And the idea of using an Exception for detecting a violation is actually appealing. Since I am not allowed to post any pictures I couldn't post the EF designer representation of the model which you have correctly setup. However I wonder if the include in your edit is actually needed. Doesn't thecode
foreachcode
with thecode
.ToList()code
force the load?
– mph
Jul 25 '11 at 8:04
@mph: TheInclude
isn't needed if you use lazy loading (your navigation properties had to bevirtual
to enable lazy loading). However you can still leverage theInclude
also with lazy loading: You would only have one roundtrip to the DB, with lazy loading you have two roundtrips. Instead of catching and evaluating the exception you could also check if the material you want to delete is used for any other material before you try to delete it (I would actually prefer this way). I've added an example in my answer how to check this.
– Slauma
Jul 25 '11 at 11:22
add a comment |
1 Answer
1
active
oldest
votes
1 Answer
1
active
oldest
votes
active
oldest
votes
active
oldest
votes
up vote
1
down vote
accepted
I'll try a halfbaked mix of answer and question. In EF 4.1 with DbContext
API I would create the following model classes (I hope I understood your description correctly):
public class Material
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<MaterialUsage> IsMadeOf { get; set; }
public ICollection<MaterialUsage> IsUsedFor { get; set; }
}
public class MaterialUsage
{
public int Id { get; set; }
public int Content { get; set; }
public Material IsUsedIn { get; set; }
public Material IsMadeOf { get; set; }
}
And this derived context and mapping:
public class MyContext : DbContext
{
public DbSet<Material> Materials { get; set; }
public DbSet<MaterialUsage> MaterialUsages { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Material>()
.HasMany(m => m.IsMadeOf)
.WithRequired(m => m.IsUsedIn)
.WillCascadeOnDelete(false);
modelBuilder.Entity<Material>()
.HasMany(m => m.IsUsedFor)
.WithRequired(m => m.IsMadeOf)
.WillCascadeOnDelete(false);
}
}
I've set the navigation properties in MaterialUsage
to Required
because I think that a MaterialUsage
cannot exist without a reference to the materials. Is that right? As far as I can see it is necessary to turn off cascading delete, otherwise EF will complain about multipe possible cascading delete paths which are not allowed.
Now, to create materials and their relationships something like this would work:
using (var context = new MyContext())
{
var copper = new Material { Name = "Copper" };
context.Materials.Add(copper);
var zinc = new Material { Name = "Zinc" };
context.Materials.Add(zinc);
var brass = new Material
{
Name = "Brass",
IsMadeOf = new List<MaterialUsage>
{
new MaterialUsage { Content = 10, IsMadeOf = copper },
new MaterialUsage { Content = 20, IsMadeOf = zinc }
}
};
context.Materials.Add(brass);
context.SaveChanges();
}
The result in the database is:
Table Materials Table MaterialUsages
Id Name Id Content IsUsedIn_Id IsMadeOf_Id
--------- -------------------------------------------
1 Brass 1 10 1 2
2 Copper 2 20 1 3
3 Zinc
Now, deleting is difficult because Material
appears in both relationships. Especially I don't know how you could accomplish this:
My intention is however to delete the material and all
'MaterialUsages' which are identified thru 'Material.IsMadeOf'
If I understand this correctly you would like to do something like this to delete zinc:
var zinc = context.Materials
.Include(m => m.IsMadeOf)
.Where(m => m.Name == "Zinc")
.Single();
foreach (var usage in zinc.IsMadeOf.ToList())
context.MaterialUsages.Remove(usage);
context.Materials.Remove(zinc);
context.SaveChanges();
This doesn't work because Zinc is made of nothing (the IsMadeOf
collection empty, so the loop above does nothing). But if you remove zinc now you violate a constraint namely that zinc is used for brass. (Id = 2 in the MaterialUsages
table cannot exist without zinc.)
I my opinion you must also delete the MaterialUsages
which are identified by Material.IsUsedFor
:
var zinc = context.Materials
.Include(m => m.IsMadeOf)
.Include(m => m.IsUsedFor)
.Where(m => m.Name == "Zinc")
.Single();
foreach (var usage in zinc.IsMadeOf.ToList())
context.MaterialUsages.Remove(usage);
foreach (var usage in zinc.IsUsedFor.ToList())
context.MaterialUsages.Remove(usage);
context.Materials.Remove(zinc);
context.SaveChanges();
This would delete Id = 3 in the Materials
table and also Id = 2 in the MaterialsUsages
table, fullfilling the referential constraints now.
Not sure if this is what you want.
Edit
I believe I see now: You actually want to have the exception which is thrown due to the violated constraint when you would delete zinc. Because: It should not be allowed to delete a material as long as it is used in another material (zinc is used in brass, therefore it's forbidden to delete zinc as long as brass is in the database). OK, then replacing zinc by brass in the example would work indeed:
var brass = context.Materials
.Include(m => m.IsMadeOf)
.Where(m => m.Name == "Brass")
.Single();
foreach (var usage in brass.IsMadeOf.ToList())
context.MaterialUsages.Remove(usage);
context.Materials.Remove(brass);
context.SaveChanges();
It just deletes both rows in the MaterialUsages
table and brass in the Material
table.
Edit 2
If you want to check if the material to delete is used for any other material you could test this before you actually try to delete:
if (context.Materials
.Where(m => m.Name == "Brass")
.Select(m => !m.IsUsedFor.Any())
.Single())
{
// the code snippet above
}
else
{
// "Brass" cannot be deleted since it is used for other materials...
}
Thank you for the detailed answer.
– mph
Jul 25 '11 at 7:52
Sorry - got tricked by the system. Thank you for the detailed answer. I am very happy that you confirm that the cascading delete is violating EF rules. And I have also been working on a delete that complies to the referential constrains. And the idea of using an Exception for detecting a violation is actually appealing. Since I am not allowed to post any pictures I couldn't post the EF designer representation of the model which you have correctly setup. However I wonder if the include in your edit is actually needed. Doesn't thecode
foreachcode
with thecode
.ToList()code
force the load?
– mph
Jul 25 '11 at 8:04
@mph: TheInclude
isn't needed if you use lazy loading (your navigation properties had to bevirtual
to enable lazy loading). However you can still leverage theInclude
also with lazy loading: You would only have one roundtrip to the DB, with lazy loading you have two roundtrips. Instead of catching and evaluating the exception you could also check if the material you want to delete is used for any other material before you try to delete it (I would actually prefer this way). I've added an example in my answer how to check this.
– Slauma
Jul 25 '11 at 11:22
add a comment |
up vote
1
down vote
accepted
I'll try a halfbaked mix of answer and question. In EF 4.1 with DbContext
API I would create the following model classes (I hope I understood your description correctly):
public class Material
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<MaterialUsage> IsMadeOf { get; set; }
public ICollection<MaterialUsage> IsUsedFor { get; set; }
}
public class MaterialUsage
{
public int Id { get; set; }
public int Content { get; set; }
public Material IsUsedIn { get; set; }
public Material IsMadeOf { get; set; }
}
And this derived context and mapping:
public class MyContext : DbContext
{
public DbSet<Material> Materials { get; set; }
public DbSet<MaterialUsage> MaterialUsages { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Material>()
.HasMany(m => m.IsMadeOf)
.WithRequired(m => m.IsUsedIn)
.WillCascadeOnDelete(false);
modelBuilder.Entity<Material>()
.HasMany(m => m.IsUsedFor)
.WithRequired(m => m.IsMadeOf)
.WillCascadeOnDelete(false);
}
}
I've set the navigation properties in MaterialUsage
to Required
because I think that a MaterialUsage
cannot exist without a reference to the materials. Is that right? As far as I can see it is necessary to turn off cascading delete, otherwise EF will complain about multipe possible cascading delete paths which are not allowed.
Now, to create materials and their relationships something like this would work:
using (var context = new MyContext())
{
var copper = new Material { Name = "Copper" };
context.Materials.Add(copper);
var zinc = new Material { Name = "Zinc" };
context.Materials.Add(zinc);
var brass = new Material
{
Name = "Brass",
IsMadeOf = new List<MaterialUsage>
{
new MaterialUsage { Content = 10, IsMadeOf = copper },
new MaterialUsage { Content = 20, IsMadeOf = zinc }
}
};
context.Materials.Add(brass);
context.SaveChanges();
}
The result in the database is:
Table Materials Table MaterialUsages
Id Name Id Content IsUsedIn_Id IsMadeOf_Id
--------- -------------------------------------------
1 Brass 1 10 1 2
2 Copper 2 20 1 3
3 Zinc
Now, deleting is difficult because Material
appears in both relationships. Especially I don't know how you could accomplish this:
My intention is however to delete the material and all
'MaterialUsages' which are identified thru 'Material.IsMadeOf'
If I understand this correctly you would like to do something like this to delete zinc:
var zinc = context.Materials
.Include(m => m.IsMadeOf)
.Where(m => m.Name == "Zinc")
.Single();
foreach (var usage in zinc.IsMadeOf.ToList())
context.MaterialUsages.Remove(usage);
context.Materials.Remove(zinc);
context.SaveChanges();
This doesn't work because Zinc is made of nothing (the IsMadeOf
collection empty, so the loop above does nothing). But if you remove zinc now you violate a constraint namely that zinc is used for brass. (Id = 2 in the MaterialUsages
table cannot exist without zinc.)
I my opinion you must also delete the MaterialUsages
which are identified by Material.IsUsedFor
:
var zinc = context.Materials
.Include(m => m.IsMadeOf)
.Include(m => m.IsUsedFor)
.Where(m => m.Name == "Zinc")
.Single();
foreach (var usage in zinc.IsMadeOf.ToList())
context.MaterialUsages.Remove(usage);
foreach (var usage in zinc.IsUsedFor.ToList())
context.MaterialUsages.Remove(usage);
context.Materials.Remove(zinc);
context.SaveChanges();
This would delete Id = 3 in the Materials
table and also Id = 2 in the MaterialsUsages
table, fullfilling the referential constraints now.
Not sure if this is what you want.
Edit
I believe I see now: You actually want to have the exception which is thrown due to the violated constraint when you would delete zinc. Because: It should not be allowed to delete a material as long as it is used in another material (zinc is used in brass, therefore it's forbidden to delete zinc as long as brass is in the database). OK, then replacing zinc by brass in the example would work indeed:
var brass = context.Materials
.Include(m => m.IsMadeOf)
.Where(m => m.Name == "Brass")
.Single();
foreach (var usage in brass.IsMadeOf.ToList())
context.MaterialUsages.Remove(usage);
context.Materials.Remove(brass);
context.SaveChanges();
It just deletes both rows in the MaterialUsages
table and brass in the Material
table.
Edit 2
If you want to check if the material to delete is used for any other material you could test this before you actually try to delete:
if (context.Materials
.Where(m => m.Name == "Brass")
.Select(m => !m.IsUsedFor.Any())
.Single())
{
// the code snippet above
}
else
{
// "Brass" cannot be deleted since it is used for other materials...
}
Thank you for the detailed answer.
– mph
Jul 25 '11 at 7:52
Sorry - got tricked by the system. Thank you for the detailed answer. I am very happy that you confirm that the cascading delete is violating EF rules. And I have also been working on a delete that complies to the referential constrains. And the idea of using an Exception for detecting a violation is actually appealing. Since I am not allowed to post any pictures I couldn't post the EF designer representation of the model which you have correctly setup. However I wonder if the include in your edit is actually needed. Doesn't thecode
foreachcode
with thecode
.ToList()code
force the load?
– mph
Jul 25 '11 at 8:04
@mph: TheInclude
isn't needed if you use lazy loading (your navigation properties had to bevirtual
to enable lazy loading). However you can still leverage theInclude
also with lazy loading: You would only have one roundtrip to the DB, with lazy loading you have two roundtrips. Instead of catching and evaluating the exception you could also check if the material you want to delete is used for any other material before you try to delete it (I would actually prefer this way). I've added an example in my answer how to check this.
– Slauma
Jul 25 '11 at 11:22
add a comment |
up vote
1
down vote
accepted
up vote
1
down vote
accepted
I'll try a halfbaked mix of answer and question. In EF 4.1 with DbContext
API I would create the following model classes (I hope I understood your description correctly):
public class Material
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<MaterialUsage> IsMadeOf { get; set; }
public ICollection<MaterialUsage> IsUsedFor { get; set; }
}
public class MaterialUsage
{
public int Id { get; set; }
public int Content { get; set; }
public Material IsUsedIn { get; set; }
public Material IsMadeOf { get; set; }
}
And this derived context and mapping:
public class MyContext : DbContext
{
public DbSet<Material> Materials { get; set; }
public DbSet<MaterialUsage> MaterialUsages { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Material>()
.HasMany(m => m.IsMadeOf)
.WithRequired(m => m.IsUsedIn)
.WillCascadeOnDelete(false);
modelBuilder.Entity<Material>()
.HasMany(m => m.IsUsedFor)
.WithRequired(m => m.IsMadeOf)
.WillCascadeOnDelete(false);
}
}
I've set the navigation properties in MaterialUsage
to Required
because I think that a MaterialUsage
cannot exist without a reference to the materials. Is that right? As far as I can see it is necessary to turn off cascading delete, otherwise EF will complain about multipe possible cascading delete paths which are not allowed.
Now, to create materials and their relationships something like this would work:
using (var context = new MyContext())
{
var copper = new Material { Name = "Copper" };
context.Materials.Add(copper);
var zinc = new Material { Name = "Zinc" };
context.Materials.Add(zinc);
var brass = new Material
{
Name = "Brass",
IsMadeOf = new List<MaterialUsage>
{
new MaterialUsage { Content = 10, IsMadeOf = copper },
new MaterialUsage { Content = 20, IsMadeOf = zinc }
}
};
context.Materials.Add(brass);
context.SaveChanges();
}
The result in the database is:
Table Materials Table MaterialUsages
Id Name Id Content IsUsedIn_Id IsMadeOf_Id
--------- -------------------------------------------
1 Brass 1 10 1 2
2 Copper 2 20 1 3
3 Zinc
Now, deleting is difficult because Material
appears in both relationships. Especially I don't know how you could accomplish this:
My intention is however to delete the material and all
'MaterialUsages' which are identified thru 'Material.IsMadeOf'
If I understand this correctly you would like to do something like this to delete zinc:
var zinc = context.Materials
.Include(m => m.IsMadeOf)
.Where(m => m.Name == "Zinc")
.Single();
foreach (var usage in zinc.IsMadeOf.ToList())
context.MaterialUsages.Remove(usage);
context.Materials.Remove(zinc);
context.SaveChanges();
This doesn't work because Zinc is made of nothing (the IsMadeOf
collection empty, so the loop above does nothing). But if you remove zinc now you violate a constraint namely that zinc is used for brass. (Id = 2 in the MaterialUsages
table cannot exist without zinc.)
I my opinion you must also delete the MaterialUsages
which are identified by Material.IsUsedFor
:
var zinc = context.Materials
.Include(m => m.IsMadeOf)
.Include(m => m.IsUsedFor)
.Where(m => m.Name == "Zinc")
.Single();
foreach (var usage in zinc.IsMadeOf.ToList())
context.MaterialUsages.Remove(usage);
foreach (var usage in zinc.IsUsedFor.ToList())
context.MaterialUsages.Remove(usage);
context.Materials.Remove(zinc);
context.SaveChanges();
This would delete Id = 3 in the Materials
table and also Id = 2 in the MaterialsUsages
table, fullfilling the referential constraints now.
Not sure if this is what you want.
Edit
I believe I see now: You actually want to have the exception which is thrown due to the violated constraint when you would delete zinc. Because: It should not be allowed to delete a material as long as it is used in another material (zinc is used in brass, therefore it's forbidden to delete zinc as long as brass is in the database). OK, then replacing zinc by brass in the example would work indeed:
var brass = context.Materials
.Include(m => m.IsMadeOf)
.Where(m => m.Name == "Brass")
.Single();
foreach (var usage in brass.IsMadeOf.ToList())
context.MaterialUsages.Remove(usage);
context.Materials.Remove(brass);
context.SaveChanges();
It just deletes both rows in the MaterialUsages
table and brass in the Material
table.
Edit 2
If you want to check if the material to delete is used for any other material you could test this before you actually try to delete:
if (context.Materials
.Where(m => m.Name == "Brass")
.Select(m => !m.IsUsedFor.Any())
.Single())
{
// the code snippet above
}
else
{
// "Brass" cannot be deleted since it is used for other materials...
}
I'll try a halfbaked mix of answer and question. In EF 4.1 with DbContext
API I would create the following model classes (I hope I understood your description correctly):
public class Material
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<MaterialUsage> IsMadeOf { get; set; }
public ICollection<MaterialUsage> IsUsedFor { get; set; }
}
public class MaterialUsage
{
public int Id { get; set; }
public int Content { get; set; }
public Material IsUsedIn { get; set; }
public Material IsMadeOf { get; set; }
}
And this derived context and mapping:
public class MyContext : DbContext
{
public DbSet<Material> Materials { get; set; }
public DbSet<MaterialUsage> MaterialUsages { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Material>()
.HasMany(m => m.IsMadeOf)
.WithRequired(m => m.IsUsedIn)
.WillCascadeOnDelete(false);
modelBuilder.Entity<Material>()
.HasMany(m => m.IsUsedFor)
.WithRequired(m => m.IsMadeOf)
.WillCascadeOnDelete(false);
}
}
I've set the navigation properties in MaterialUsage
to Required
because I think that a MaterialUsage
cannot exist without a reference to the materials. Is that right? As far as I can see it is necessary to turn off cascading delete, otherwise EF will complain about multipe possible cascading delete paths which are not allowed.
Now, to create materials and their relationships something like this would work:
using (var context = new MyContext())
{
var copper = new Material { Name = "Copper" };
context.Materials.Add(copper);
var zinc = new Material { Name = "Zinc" };
context.Materials.Add(zinc);
var brass = new Material
{
Name = "Brass",
IsMadeOf = new List<MaterialUsage>
{
new MaterialUsage { Content = 10, IsMadeOf = copper },
new MaterialUsage { Content = 20, IsMadeOf = zinc }
}
};
context.Materials.Add(brass);
context.SaveChanges();
}
The result in the database is:
Table Materials Table MaterialUsages
Id Name Id Content IsUsedIn_Id IsMadeOf_Id
--------- -------------------------------------------
1 Brass 1 10 1 2
2 Copper 2 20 1 3
3 Zinc
Now, deleting is difficult because Material
appears in both relationships. Especially I don't know how you could accomplish this:
My intention is however to delete the material and all
'MaterialUsages' which are identified thru 'Material.IsMadeOf'
If I understand this correctly you would like to do something like this to delete zinc:
var zinc = context.Materials
.Include(m => m.IsMadeOf)
.Where(m => m.Name == "Zinc")
.Single();
foreach (var usage in zinc.IsMadeOf.ToList())
context.MaterialUsages.Remove(usage);
context.Materials.Remove(zinc);
context.SaveChanges();
This doesn't work because Zinc is made of nothing (the IsMadeOf
collection empty, so the loop above does nothing). But if you remove zinc now you violate a constraint namely that zinc is used for brass. (Id = 2 in the MaterialUsages
table cannot exist without zinc.)
I my opinion you must also delete the MaterialUsages
which are identified by Material.IsUsedFor
:
var zinc = context.Materials
.Include(m => m.IsMadeOf)
.Include(m => m.IsUsedFor)
.Where(m => m.Name == "Zinc")
.Single();
foreach (var usage in zinc.IsMadeOf.ToList())
context.MaterialUsages.Remove(usage);
foreach (var usage in zinc.IsUsedFor.ToList())
context.MaterialUsages.Remove(usage);
context.Materials.Remove(zinc);
context.SaveChanges();
This would delete Id = 3 in the Materials
table and also Id = 2 in the MaterialsUsages
table, fullfilling the referential constraints now.
Not sure if this is what you want.
Edit
I believe I see now: You actually want to have the exception which is thrown due to the violated constraint when you would delete zinc. Because: It should not be allowed to delete a material as long as it is used in another material (zinc is used in brass, therefore it's forbidden to delete zinc as long as brass is in the database). OK, then replacing zinc by brass in the example would work indeed:
var brass = context.Materials
.Include(m => m.IsMadeOf)
.Where(m => m.Name == "Brass")
.Single();
foreach (var usage in brass.IsMadeOf.ToList())
context.MaterialUsages.Remove(usage);
context.Materials.Remove(brass);
context.SaveChanges();
It just deletes both rows in the MaterialUsages
table and brass in the Material
table.
Edit 2
If you want to check if the material to delete is used for any other material you could test this before you actually try to delete:
if (context.Materials
.Where(m => m.Name == "Brass")
.Select(m => !m.IsUsedFor.Any())
.Single())
{
// the code snippet above
}
else
{
// "Brass" cannot be deleted since it is used for other materials...
}
edited Jul 25 '11 at 11:21
answered Jul 21 '11 at 12:34
Slauma
146k51355383
146k51355383
Thank you for the detailed answer.
– mph
Jul 25 '11 at 7:52
Sorry - got tricked by the system. Thank you for the detailed answer. I am very happy that you confirm that the cascading delete is violating EF rules. And I have also been working on a delete that complies to the referential constrains. And the idea of using an Exception for detecting a violation is actually appealing. Since I am not allowed to post any pictures I couldn't post the EF designer representation of the model which you have correctly setup. However I wonder if the include in your edit is actually needed. Doesn't thecode
foreachcode
with thecode
.ToList()code
force the load?
– mph
Jul 25 '11 at 8:04
@mph: TheInclude
isn't needed if you use lazy loading (your navigation properties had to bevirtual
to enable lazy loading). However you can still leverage theInclude
also with lazy loading: You would only have one roundtrip to the DB, with lazy loading you have two roundtrips. Instead of catching and evaluating the exception you could also check if the material you want to delete is used for any other material before you try to delete it (I would actually prefer this way). I've added an example in my answer how to check this.
– Slauma
Jul 25 '11 at 11:22
add a comment |
Thank you for the detailed answer.
– mph
Jul 25 '11 at 7:52
Sorry - got tricked by the system. Thank you for the detailed answer. I am very happy that you confirm that the cascading delete is violating EF rules. And I have also been working on a delete that complies to the referential constrains. And the idea of using an Exception for detecting a violation is actually appealing. Since I am not allowed to post any pictures I couldn't post the EF designer representation of the model which you have correctly setup. However I wonder if the include in your edit is actually needed. Doesn't thecode
foreachcode
with thecode
.ToList()code
force the load?
– mph
Jul 25 '11 at 8:04
@mph: TheInclude
isn't needed if you use lazy loading (your navigation properties had to bevirtual
to enable lazy loading). However you can still leverage theInclude
also with lazy loading: You would only have one roundtrip to the DB, with lazy loading you have two roundtrips. Instead of catching and evaluating the exception you could also check if the material you want to delete is used for any other material before you try to delete it (I would actually prefer this way). I've added an example in my answer how to check this.
– Slauma
Jul 25 '11 at 11:22
Thank you for the detailed answer.
– mph
Jul 25 '11 at 7:52
Thank you for the detailed answer.
– mph
Jul 25 '11 at 7:52
Sorry - got tricked by the system. Thank you for the detailed answer. I am very happy that you confirm that the cascading delete is violating EF rules. And I have also been working on a delete that complies to the referential constrains. And the idea of using an Exception for detecting a violation is actually appealing. Since I am not allowed to post any pictures I couldn't post the EF designer representation of the model which you have correctly setup. However I wonder if the include in your edit is actually needed. Doesn't the
code
foreachcode
with the code
.ToList()code
force the load?– mph
Jul 25 '11 at 8:04
Sorry - got tricked by the system. Thank you for the detailed answer. I am very happy that you confirm that the cascading delete is violating EF rules. And I have also been working on a delete that complies to the referential constrains. And the idea of using an Exception for detecting a violation is actually appealing. Since I am not allowed to post any pictures I couldn't post the EF designer representation of the model which you have correctly setup. However I wonder if the include in your edit is actually needed. Doesn't the
code
foreachcode
with the code
.ToList()code
force the load?– mph
Jul 25 '11 at 8:04
@mph: The
Include
isn't needed if you use lazy loading (your navigation properties had to be virtual
to enable lazy loading). However you can still leverage the Include
also with lazy loading: You would only have one roundtrip to the DB, with lazy loading you have two roundtrips. Instead of catching and evaluating the exception you could also check if the material you want to delete is used for any other material before you try to delete it (I would actually prefer this way). I've added an example in my answer how to check this.– Slauma
Jul 25 '11 at 11:22
@mph: The
Include
isn't needed if you use lazy loading (your navigation properties had to be virtual
to enable lazy loading). However you can still leverage the Include
also with lazy loading: You would only have one roundtrip to the DB, with lazy loading you have two roundtrips. Instead of catching and evaluating the exception you could also check if the material you want to delete is used for any other material before you try to delete it (I would actually prefer this way). I've added an example in my answer how to check this.– Slauma
Jul 25 '11 at 11:22
add a comment |
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Some of your past answers have not been well-received, and you're in danger of being blocked from answering.
Please pay close attention to the following guidance:
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f6774794%2fnm-with-payload-in-entity-framework-4-x%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown