I have multiple objects with subset of fields from the table. In some cases I should update only one field. How to do this properly in Entity Framework 6.0? The following code throws error due to database constraints because AddOrUpdate tries to replace all fields but FieldName with empty values.
public static TheField Set(TheField f)
{
using (var dbContext = new MyModel())
{
dbContext.MyEntity.AddOrUpdate(new MyEntity()
{
ForeignId = f.ForeignId,
FieldName = f.FieldName,
});
dbContext.SaveChanges();
return f;
}
}
It would be nice to have an extension
public static class MyExtension
{
public static void AddOrUpdateSchema<TEntity, TKey>(this IDbSet<TEntity> set, TKey id, string schema,
params TEntity[] entities) where TEntity : class
{
// ...
}
}
and then use it
public class MyEntity
{
[Key]
public int ForeignId { get; set; }
[UpdateSchema("Schema")]
public string FieldName { get; set; }
// ...
}
public class MyEntityView
{
public int ForeignId { get; set; }
public string FieldName { get; set; }
public static MyEntityView Set(MyEntityView f)
{
using (var dbContext = new MyModel())
{
dbContext.MyEntity.AddOrUpdateSchema(f.ForeignId, "Schema", new MyEntity()
{
FieldName = f.FieldName,
});
dbContext.SaveChanges();
return f;
}
}
}
Or maybe Entity Framework already has functionality for this task?
Set State property to EntityState.Modified
db.Entry(entity).State = EntityState.Modified;
Please check if below code works for you:
using (var dbContext = new MyModel())
{
if (dbContext.MyEntities.Any(e => e.ForeignId == f.ForeignId))
{
dbContext.MyEntities.Attach(f);
dbContext.ObjectStateManager.ChangeObjectState(f, EntityState.Modified);
}
else
{
dbContext.MyEntities.AddObject(f);
}
dbContext.SaveChanges();
}
Related
I'm trying to recreate the sticky notes app in windows 10 using WPF.
I am using Entity Framework with an SQLite database. And EF can't retrieve an object which is the child of another. How can I do that?
I have basically 2 classes i wanna store in this database
StickyNote and SitckyNoteGroup. Each StickyNote has a StickyNoteGroup.
In order you to fully understand I'll post all the classes involved in my problem but here is the github repo if you want https://github.com/Kamigoro/StickyNotes.
namespace Todo.Models
{
public class StickyNote
{
public int Id { get; set; }
public string Name { get; set; }
public string Text { get; set; }
public StickyNoteGroup Group { get; set; }
public override string ToString()
{
return $"Name : {Name}\nText : {Text}";
}
}
}
namespace Todo.Models
{
public class StickyNoteGroup
{
public int Id { get; set; }
public string Name { get; set; }
public string Color { get; set; }
public override string ToString()
{
return $"Name : {Name}\nColor : {Color}";
}
}
}
My class using EntityFramework looks like this and is called NoteContext
using Microsoft.EntityFrameworkCore;
namespace Todo.Models.DataAccess
{
public class NoteContext : DbContext
{
public DbSet<StickyNote> Notes { get; set; }
public DbSet<StickyNoteGroup> NoteGroups { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder options)
=> options.UseSqlite("Data Source=Todo.db");
}
}
And finally the class that I use to do CRUD operations is called DataAccessor and looks like this.
namespace Todo.Models.DataAccess
{
public static class DataAccessor
{
public static List<StickyNote> GetStickyNotes()
{
List<StickyNote> notes;
using (var database = new NoteContext())
{
notes = database.Notes.ToList();
}
return notes;
}
public static void SaveStickyNote(StickyNote stickyNote)
{
using (var database = new NoteContext())
{
database.Notes.Add(stickyNote);
database.SaveChanges();
}
}
public static List<StickyNoteGroup> GetStickyNoteGroups()
{
List<StickyNoteGroup> noteGroups;
using (var database = new NoteContext())
{
noteGroups = database.NoteGroups.ToList();
}
return noteGroups;
}
public static void SaveStickyNoteGroup(StickyNoteGroup stickyNoteGroup)
{
using (var database = new NoteContext())
{
database.NoteGroups.Add(stickyNoteGroup);
database.SaveChanges();
}
}
}
}
My question is why this part of code tell me that there is no StickyNoteGroup for the current StickyNote, even though there is one in the sqlite database?
private void btnAddStickyNote_Click(object sender, RoutedEventArgs e)
{
StickyNoteGroup group = new StickyNoteGroup() { Name = "Test Group", Color = "Red" };
StickyNote note = new StickyNote() { Name = "Note 1", Text = "An attempt to write a note", Group = group };
DataAccessor.SaveStickyNote(note);
foreach (StickyNote currentNote in DataAccessor.GetStickyNotes())
{
Debug.WriteLine(currentNote.Group.ToString());
}
}
StickyNote Table:
StickyNoteGroup Table:
Thanks a lot for your answers. And if you have other comments to do on my code, they are welcomed, because i don't really know the good patterns to work with an ORM like that.
It seems that you need to add Include call:
public static List<StickyNote> GetStickyNotes()
{
List<StickyNote> notes;
using (var database = new NoteContext())
{
notes = database.Notes
.Include(n => n.Group)
.ToList();
}
return notes;
}
BTW you can return evaluated query result data (for example via ToList, ToArray, First and so on) from inside using statement:
public static List<StickyNote> GetStickyNotes()
{
using (var database = new NoteContext())
{
return database.Notes
.Include(n => n.Group)
.ToList();
}
}
I am having trouble with generics. Using examples from here, I want to pass a property for use in EF Core. I have a feeling I am overcomplicating things. Note that I would like to stick with lamba expressions instead of passing something like nameof (which I cannot use in 4.0).
class Foo
{
public string A { get; set; } // some unique string in DbSet for entity Person
public string B { get; set; } // some unique string in DbSet for entity Company
public int GetId<TEntity>(string match, Expression<Func<TEntity, string>> fieldToExamine, Expression<Func<TEntity, int>> fieldWithId, System.Data.Entity.DbContext context)
where TEntity : class
{
int retval = -1;
if (!string.IsNullOrEmpty(match))
{
var expr = (MemberExpression)fieldToExamine.Body;
var prop = (PropertyInfo)expr.Member;
var expr2 = (MemberExpression)fieldWithId.Body;
var idField = (PropertyInfo)expr2.Member;
// this works because everything is explicit
var entity = context.Set<Person>().SingleOrDefault(p => p.PersonName.Equals(match));
// ... but I want that to be generic
// how to reference the property to be evaluated?
//var entity = context.Set<TEntity>().SingleOrDefault(p => ...); // <- HELP?!
if (entity != null)
{
retval = (int)entity.GetType().GetProperty(idField.Name).GetValue(entity, null);
}
}
return retval;
}
}
static void Main(string[] args)
{
// .. omitted database stuff ..
// (for Person) what I want is to match the Name field (here: p.Name) of the Person entity to the Foo property value (here: f.A) and return the database id stored in p.PersonId
int x = f.GetId<Person>(f.A, p => p.PersonName, p => p.PersonId, context);
// same for Company, but different values and fields
int y = f.GetId<Company>(f.B, p => p.CompanyName, p => p.CompanyId, context);
}
Person class and Company class will need to implement either abstract class or interface.
public abstract class BaseEntity
{
public abstract int Id { get; set; }
public abstract string Name { get; set; }
}
public class Person : BaseEntity
{
public int PersonId { get; set; }
public override string Name { get; set; }
public override int Id
{
get { return PersonId; }
set { PersonId = value; }
}
}
public class Company : BaseEntity
{
public int CompanyId { get; set; }
public override string Name { get; set; }
public override int Id
{
get { return CompanyId; }
set { CompanyId = value; }
}
}
Then you can pass p.Name and p.CompanyId.
var entity = context.Set<TEntity>().SingleOrDefault(p => p.Name.Equals(match));
I have my models setup like this...
public class Model1 : IEquatable<Model1>
{
public int Model1Id { get; set; }
public string Name1 { get; set; }
public Model2 Model2 { get; set; }
public int Model2Id { get; set; }
public bool Equals(Model1 other)
{
return this.Model2.Equals(other.Model2)
&& this.Name1 == other.Name1;
}
}
public class Model2 : IEquatable<Model2>
{
public int Model2Id { get; set; }
public string Name2 { get; set; }
public bool Equals(Model2 other)
{
return this.Name2 == other.Name2;
}
}
public class ModelContext : DbContext
{
public DbSet<Model1> Model1 { get; set; }
public DbSet<Model2> Model2 { get; set; }
public ModelContext(DbContextOptions<ModelContext> options) : base(options) { }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Model1>(b =>
{
b.HasOne(m1 => m1.Model2).WithMany().HasForeignKey(m1 => m1.Model2Id);
});
}
}
then I get a null reference exception when I do this...
static void Main(string[] args)
{
var myModel1 = new Model1
{
Name1 = "myName1",
Model2 = new Model2
{
Name2 = "myName2"
}
};
var connection = new SqliteConnection("DataSource=:memory:");
connection.Open();
try
{
var options = new DbContextOptionsBuilder<ModelContext>()
.UseSqlite(connection)
.Options;
//create database
using(var ctx = new ModelContext(options))
{
ctx.Database.EnsureCreated();
}
//add model objects
using (var ctx = new ModelContext(options))
{
ctx.Database.EnsureCreated();
ctx.Model1.Add(myModel1);
ctx.SaveChanges();
}
//check if exists
using(var ctx = new ModelContext(options))
{
//exception here
bool isExists = ctx.Model1.Include(m1 => m1.Model2).Contains(myModel1);
Console.WriteLine(isExists);
}
}
finally
{
connection.Close();
}
Console.ReadKey();
}
I'm expeting the Model2 instance of my m1 to be populated when I call the Include but it is still null.
but If I add AsEnumerable() to my query like..
ctx.Model1.Include(m1 => m1.Model2).AsEnumerable().Contains(model1);
then everything works fine.
EDIT:
my question is... why do I need to call AsEnumerable()? I was expecting it to work without calling AsEnumerable()..
The difference is one is an entityframe work call the other is linq to objects
Entity Framework Does not understand contains for a CLR Object
public void AddIfNotExists(Model1 model1)
{
//No Need for the include this is executed in sql, assuming the model 2
//property has already been included in your model1 this should work fine
if(false == _context.Model1.Any(x => x.Name1 == model1.Name1
&& x.Model2.Name2 == model1.Model2.Name2))
{
_context.Model1.Add(model1);
}
}
I made this based off of your logic, but chances are you really just want to check if model1.id is the the model1 set. But I have no Idea what your architecture is doing so this is what you probably want
Environment:
I am working in Webapi. There is 2 entity classes which are follows;
public class Class1
{
public Class1()
{
this.items = new HashSet<Class2>();
}
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Class2> items { get; set; }
}
public class Class2
{
public int Id { get; set; }
public string Name { get; set; }
public int Class1Id { get; set; }
public virtual Class1 class1 { get; set; }
}
Business Layer:
The buniess layer have the following codes;
public class Class1Logic : IClass1Logic
{
private readonly IClass1Repository _repo;
public Class1Logic(IClass1Repository repository)
{
_repo = repository;
}
public async Task<bool> AddClass1ItemAsync(Class1 item)
{
_repo.Add(item);
bool status = await _repo.SaveAsync();
return status;
}
public async Task<Class1> GetClass1ItemAsync(int id)
{
return await _repo.GetAsync(id);
}
}
public class Class2Logic : IClass1Logic
{
private readonly IClass2Repository _repo;
public Class2Logic(IClass2Repository repository)
{
_repo = repository;
}
public async Task<bool> AddClass2ItemAsync(Class2 item)
{
_repo.Add(item);
bool status = await _repo.SaveAsync();
return status;
}
public async Task<Class2> GetClass2ItemAsync(int id)
{
return await _repo.GetAsync(id);
}
}
ViewModels:
public class Class1Model
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Class2Model
{
public int Id { get; internal set; }
public string Name { get; set; }
public int Class1Id { get; set; }
public string Class1Name { get; internal set; }
}
Controllers:
There are 2 contrtollers like Class1Controller and Class2Controller. Both have all CRUD operations.
[RoutePrefix("api/class1items")]
public class Class1Controller : ApiController
{
private readonly IClass1Logic _class1Logic;
private ModelFactory TheFactory;
public Class1Controller(IClass1Logic class1Logic)
{
_class1Logic = class1Logic;
TheFactory = new ModelFactory();
}
[Route("")]
public async Task<IHttpActionResult> Post(Class1Model class1Model)
{
var item = TheFactory.Parse(class1Model);
bool result = await _class1Logic.AddClassItemAsync(item);
if (!result)
{
return BadRequest("Error");
}
string uri = Url.Link("GetLabById", new { id = item.Id });
return Created(uri, TheFactory.Create(item));
}
[Route("{id:int}", Name = "GetClass1ItemById")]
public async Task<IHttpActionResult> GetClass1Item(int id)
{
Class1 item = await _class1Logic.GetClassItemAsync(id);
if (item == null)
{
return NotFound();
}
return Ok(TheFactory.Create(item));
}
}
[RoutePrefix("api/class2items")]
public class Class2Controller : ApiController
{
private readonly IClass2Logic _class2Logic;
private ModelFactory TheFactory;
public Class2Controller(IClass2Logic class2Logic)
{
_class2Logic = class2Logic;
TheFactory = new ModelFactory();
}
[Route("")]
public async Task<IHttpActionResult> Post(Class2Model class2Model)
{
var item = TheFactory.Parse(class2Model);
***//Here item should include Class1 object even if user give ClassId in class2Model***
bool result = await _class2Logic.AddClassItemAsync(item);
if (!result)
{
return BadRequest("Error");
}
string uri = Url.Link("GetClass2ItemById", new { id = item.Id });
return Created(uri, TheFactory.Create(item));
}
}
There is not dependecies in Class1. So all operations are fine. In Class2Controller post method, I got the model object as following to create Class2.
{
"id": 0,
"name": "string",
"class1Id": 1
}
Understanding:
I need to return this viewmodel to user after the create the record. The record created successfully but when mapping to viewmodel i got null exception as Class1 object not in the Class2 object.
In order to get the Class2 object including class1 object, I need to give the class1Object in the request object.
For this i need to find the Class1 object with Class1Id in the request object.
ViewMapper Code:
public class ModelFactory
{
public Class1Model Create(Class1 item)
{
return new Class1Model
{
Id = item.Id,
Name = item.Name
};
}
public Class2Model Create(Class2 item)
{
return new Class2Model
{
Id = item.Id,
Name = item.Name,
Class1Id = item.class1.Id,
Class1Name = item.class1.Name
};
}
public Class1 Parse(Class1Model modelItem)
{
return new Class1
{
Id = modelItem.Id,
Name = modelItem.Name
};
}
public Class2 Parse(Class2Model modelItem)
{
return new Class2
{
Id = modelItem.Id,
Name = modelItem.Name,
Class1Id = modelItem.Class1Id,
***/*Issue Place*/
//class1 = Need to set property by getting object using modelItem.Class1Id***
};
}
}
Issue:
Now i need to call get method of Class1Controller by passing Class1Id.
How to call and is this correct? or my design is bad?
This is initial case. If my Class3 have both Class1 and Class2 again i need to call methods of Class1 and Class2.
Please help to find the correct solution in this case
Note: I added comments the issue area to understand
Well, just to fix this issue you need to manually call _class1Logic.GetClass1ItemAsync after saving. However this doesn't look good.
More elegant ways to fix it:
1) If you always need Class2.Class1 field to be filled use Include when you fetch data (in repository): dbContext.Set<Class2>().Include(c => c.class1).
2) Also you can turn on LazyLoading for EF - I assume it should work in your case.
3) Inject class1Repo to class2Logic and fix up class1 reference after saving - in case if you don't want to enable lazy loading or item was detached from context after save method
Thoughts about design:
I suggest you to look at Automapper or simular libraries instead of ModelFactory where you going to have all mapping logic
Edit: About generic repository: you can modify you GetAsync method
public async Task<T> GetAsync<T>(int id, params Expression<Func<T, object>>[] includes)
where T: class, IEntity
{
var query = context.Set<T>().AsQueryable();
if (includes.Length > 0)
{
query = includes.Aggregate(query,
(current, include) => current.Include(include));
}
return await query.FirstOrDefaultAsync(x => x.Id == id);
}
IEntity interface:
interface IEntity
{
int Id { get; }
}
With this implementation you can use
await _repo.GetAsync<Class2>(id, x => x.class1);
I have create a Generic Repository (Using EF 6.1.1), which I am using in several projects and it works very well.
I have implemented a 'soft' delete feature, where we mark data as deleted, but do not actually remove it from the database.
I can then filter all my queries as all entities inherit from a base entity which has the IsDeleted property. This is all works nicely, but it obviously does not filter out any of the 'soft deleted' child entities.
I am unsure how to go about doing this in a generic way, as I dont want to have to over code a solution into every respoitory, that really defeats the reason for having a generic repo.
this is an example of my current Generic Repo
public sealed class MyRepository<T> : IRepository<T> where T : BaseEntity
{
public String CurrentUser { get; set; }
private readonly MyObjectContext context;
private readonly Configuration configuration = ConfigurationManager.GetConfiguration();
private IDbSet<T> entities;
private IDbSet<T> Entities
{
get { return entities ?? (entities = context.Set<T>()); }
}
public MyRepository(MyObjectContext context, String userName = null)
{
this.context = context;
var providerManager = new DataProviderManager(configuration);
var dataProvider = (IDataProvider)providerManager.LoadDataProvider();
dataProvider.InitDatabase();
CurrentUser = userName;
}
public void Dispose()
{
//do nothing at the moment
}
public T GetById(Guid id)
{
return Entities.FirstOrDefault(x => x.Id == id && !x.IsDeleted);
}
public IQueryable<T> GetAll()
{
return Entities.Where(x => !x.IsDeleted);
}
public IQueryable<T> Query(Expression<Func<T, bool>> filter)
{
return Entities.Where(filter).Where(x => !x.IsDeleted);
}
public void Delete(T entity)
{
if (configuration.HardDelete)
{
HardDelete(entity);
}
else
{
SoftDelete(entity);
}
}
private void HardDelete(T entity)
{
try
{
if (entity == null)
{
throw new ArgumentNullException("entity");
}
Entities.Attach(entity);
Entities.Remove(entity);
}
catch (DbEntityValidationException ex)
{
var msg = string.Empty;
foreach (var validationErrors in ex.EntityValidationErrors)
foreach (var validationError in validationErrors.ValidationErrors)
msg += Environment.NewLine + string.Format("Property: {0} Error: {1}", validationError.PropertyName, validationError.ErrorMessage);
var fail = new Exception(msg, ex);
throw fail;
}
}
private void SoftDelete(T entity)
{
entity.IsDeleted = true;
Update(entity);
}
}
Any help on this would be great.
Thanks
Someone has built a global filter, you can try it and install it from nuget EntityFramework.Filters.
https://github.com/jbogard/EntityFramework.Filters
Here is an example how to use it.
public abstract class BaseEntity
{
public int Id { get; set; }
public bool IsDeleted { get; set; }
}
public class Foo : BaseEntity
{
public string Name { get; set; }
public ICollection<Bar> Bars { get; set; }
}
public class Bar : BaseEntity
{
public string Name { get; set; }
public int FooId { get; set; }
public Foo Foo { get; set; }
}
public class AppContext : DbContext
{
public DbSet<Foo> Foos { get; set; }
public DbSet<Bar> Bars { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// Registers and configures it first.
DbInterception.Add(new FilterInterceptor());
var softDeleteFilter = FilterConvention.Create<BaseEntity>("SoftDelete",
e => e.IsDeleted == false); // don't change it into e => !e.IsDeleted
modelBuilder.Conventions.Add(softDeleteFilter);
}
}
Then you can enable it in your repository constructor or somewhere after db context instance is created because the filters are disabled by default.
using (var db = new AppContext())
{
db.EnableFilter("SoftDelete");
var foos = db.Foos.Include(f => f.Bars).ToArray(); // works on Include
}
using (var db = new AppContext())
{
db.EnableFilter("SoftDelete");
var foos = db.Foos.ToArray();
foreach (var foo in foos)
{
var bars = foo.Bars; // works on lazy loading
}
}
using (var db = new AppContext())
{
db.EnableFilter("SoftDelete");
var foos = db.Foos.ToArray();
foreach (var foo in foos)
{
db.Entry(foo).Collection(f => f.Bars).Load(); // works on manual loading
}
}
This filter is not needed anymore.
public IQueryable<T> Query(Expression<Func<T, bool>> filter)
{
return Entities.Where(filter);//.Where(x => !x.IsDeleted);
}
As long as you have enabled it.
public MyRepository(MyObjectContext context, String userName = null)
{
this.context = context;
if (!configuration.HardDelete)
{
this.context.EnableFilter("SoftDelete");
}
}
I was having the same problem but my method to solve was little different i used a genric base interface IGenricInterface with IsDeleted as property
public int DeletebyId(string Id)
{
var Ent = (IGenricInterface)_sitecontext.Set<TEntity>().Find(Id);
Ent.IsDeleted = 1;
}
and for hardDelete
_sitecontext.Set<TEntity>().Remove(Ent);
This is on ID but offcourse you can do it on EnTity as well