most efficient way to process a variable size dataset - c#

I'm looking for advice on the most efficient way to process a variable size dataset.i have a user requirement to provide a web interface to enable the user to upload an excel sheet containing a list of record ids, the fields to update and the new value, each row can be a different field and a different value and the number of rows can vary from a few dozen up to around 20,000. The destination table is in a Microsoft SQL database
The technology stack I'm using is C#, MVC using WCF to a custom ESB, MSMQ, Entity Framework(but i can't change table structure to enable optimistic concurrency) and MS SQL.
So parsing the datasource is fine but i'm unsure as to best way to proceed from there.
Am i better of creating an individual message for each row or do should i parse the result set and group messages where possible(i.e where the field name and value match) into a single larger update statement and pass that as the message
And am i better to update via Entity Framework directly or use a stored procedure?

Here's a little helper method to update an EF entity based on a list of name/value pairs;
public void Update<T>(T entity, Dictionary<string, string> valuesToUpdate) where T : class
{
var entry = ChangeTracker.Entries<T>().Where(e => object.ReferenceEquals(e.Entity, entity)).Single();
foreach (var name in valuesToUpdate.Keys)
{
var pi = typeof(T).GetProperty(name);
pi.SetValue(entity, Convert.ChangeType(valuesToUpdate[pi.Name], pi.PropertyType));
entry.Property(pi.Name).IsModified = true;
}
}
And a full example of how you would use it:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity;
using System.Linq;
namespace Ef6Test
{
public class Car
{
public int Id { get; set; }
public string Name { get; set; }
public int Color { get; set; }
public DateTime UpdateDate { get; set; }
}
class Db : DbContext
{
public void Update<T>(T entity, Dictionary<string, string> valuesToUpdate) where T : class
{
var entry = ChangeTracker.Entries<T>().Where(e => object.ReferenceEquals(e.Entity, entity)).Single();
foreach (var name in valuesToUpdate.Keys)
{
var pi = typeof(T).GetProperty(name);
pi.SetValue(entity, Convert.ChangeType(valuesToUpdate[pi.Name], pi.PropertyType));
entry.Property(pi.Name).IsModified = true;
}
}
public DbSet<Car> Cars { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
}
class Program
{
static void Main(string[] args)
{
Database.SetInitializer(new DropCreateDatabaseAlways<Db>());
using (var db = new Db())
{
db.Database.Log = m => Console.WriteLine(m);
db.Database.Initialize(true);
}
int id;
using (var db = new Db())
{
db.Database.Log = m => Console.WriteLine(m);
var c = db.Cars.Create();
c.Color = 2;
c.UpdateDate = DateTime.Now;
db.Cars.Add(c);
db.SaveChanges();
id = c.Id;
}
using (var db = new Db())
{
db.Database.Log = m => Console.WriteLine(m);
var c = new Car() { Id = id };
var updates = new Dictionary<string, string>();
updates.Add(nameof(Car.Color), "3");
updates.Add(nameof(Car.UpdateDate), "2017-01-02");
db.Cars.Attach(c);
db.Update(c, updates);
db.SaveChanges();
}
Console.WriteLine("Hit any key to exit");
Console.ReadKey();
}
}
}
}
And here's the UPDATE EF Generates:
UPDATE [dbo].[Cars]
SET [Color] = #0, [UpdateDate] = #1
WHERE ([Id] = #2)
-- #0: '3' (Type = Int32)
-- #1: '1/2/2017 12:00:00 AM' (Type = DateTime2)
-- #2: '1' (Type = Int32)
Note that only the changed attributes are modified, Name is not.

I always want to go for type safety. Therefore I would create a class to display your values and use a generic adapter class that handles the fetching and the updating of the values of your database.
Your display class will need something like this:
abstract class DisplayedValue
{
public int Id {get; protected set;}
public string FieldDescription {get; protected set;}
public abstract string Value {get; set;}
}
We want the compiler to complain if you try to assign an integer value to a DateTime, or other invalid conversions. So we need a generic class that will hold the fetched value, and translate the displayed value into a fetched value
class Display<Tproperty> : Display
{
public override string Value
{
get {return this.FetchValue.ToString();}
set {this.SetValue(Parse(value));}
}
public Func<string, TProperty> Parse {get; set;}
public Func<int, TProperty> FetchValue {get; set;}
public Action <int, TProperty> SetValue {get; set;}
}
This class represents the original value of the property you want to display. Because I don't know the type of items you want to display in your rows (simple numbers? Guids? Customer names?), I need a Parse function that parses the string to update into a value to update.
TODO: if ToString() is not suitable to convert your property into the displayed value, consider using a Func property that converts your TProperty into a DisplayValue:
public Func<TProperty, string> ToDisplayValue {get; set;}
TODO: to increase performance, consider keeping track on whether the data has already been fetched and translated, and not fetch / translate it again if asked for.
FetchValue is a Function that takes an int Id, and returns the Tproperty value of the item that must be displayed.
UpdateValue is a void Function that takes as input an Id, and a Tproperty value to update. It updates the proper value
So to create a Display object you need:
Id to display
FieldDescription
Parse function that parses the displayed value into a TProperty value
A function to fetch the data
A void function to update the data
Did you notice, that in this class I never mentioned that I use a database to fetch or to update the data. This is hidden in the delegate functions to Fetch and Update the data. This allows reuse to store data in other media, like variables, streams, files, etc
As an example: a SchoolDbContext with a Student:
class Student
{
public int Id {get; set;} // primary Key
public DateTime Birthday {get; set;
public string FirstName {get; set;}
... // other properties
}
class SchoolDbContext : DbContext
{
public DbSet<Student> Students {get; set;} // the table you want to update
... // other tables
}
Suppose you want Display a row that can update the Birthday of the Student with Id myStudentId.
int myStudentId = ...
MyDbContext myDbContext = ...
DisplayedValue birthday = new Display<DateTime>()
{
Id = myStudentId,
FieldDescription = "Birthday",
// Parse function to parse the update string to a DateTime
Parse = (txt) => DateTime.Parse(txt),
// function to parse the DateTime to a displayable string
ToDisplayValue = (birthday) => birthDay.ToString("yyyy/MMM/DD"),
// the function that fetches the Birthday of Student with Id from myDbContext:
FetchValue = (id) => myDbContext.Students
.Where(student => student.Id == id)
.Select(student => student.Birthday)
.SingleOrDefault();
// the function that updates the Birthday of the Student with Id from myDbContext:
UpdateValue = (id, valueToUpdate) =>
{
Student studentToUpdate = dbContext.Students
.Where(student => student.Id == id)
.SingleOrDefault();
studentToUpdate.BirthDay = valueToUpdate);
myDbContext.SaveChanges();
},
}
Although this is a very neat and reusable solution it is quite a lot of work for every item you want to display. If you want to automate this in a factory, you run into several problems
You need to be sure every item needs to have an Id
How to get the descriptive name of the displayed item? Is property name enough?
.
interface IId
{
int Id {get;}
}
You need to be sure that every class in your DbContext that will be a DbSet derives from this interface.
public DisplayFactory
{
public MyDbContext MyDbContext {get; set;}
public Display<TProperty> Create<TEntity, TProperty>(int id,
Expression<Func<TEntity, TProperty>> propertySelector,
Action<TEntity, TProperty> propertyUpdater,
Func<string, TProperty> parse,
Func<TProperty, string> toDisplayValue)
{
return new Display<TProperty>()
{
Id = id,
Parse = parse,
ToDisplayValue = toDisplayValue,
FetchValue = (id) => this.MyDbContext.DbSet<TEntity>()
.Where(entity => entity.Id == id) // this is where I need the interface
.Select(propertySelector)
.SingleOrDefault(),
SetValue = (id, valueToUpdate) =>
{
TEntity entityToUpdate = this.MyDbContext.DbSet<TEntity>()
.Where(entity => entity.Id == id)
.SingleOrDefault();
propertyUpdate(entityToUpdate, valueToUpdate);
SaveChanges();
}
}
}
Usage:
DisplayFactory factory = new DisplayFactory()
{
MyDbContext = ...
}
DisplayedValue createdValue = factory.Create(id,
student => student.Birthday, // property selector
(student, value) => student.Birthday = value; // property updater
(txt) => DateTime.Parse(txt), // string to Datetime
(birthday) => birthDay.ToString(...)); // to displayed birthday
Note, this is completely type safe, The compiler won't accept it if you want to update non-existing columns or non-existing types or want to assign incompatible types, like assigning an int to a DateTime. You can't by accident update another property than you just displayed.
If you still think that this is too much work, consider using reflection and PropertyInfo to select the DbSetand the Column you want to update.
Keep in mind however, that you still have to provide parsers to display and parse the displayed string values into values to update. You'll loose all type safety and the compiler will accept it if you use names of non-existing tables or columns.
I'm not sure if the extra testing time weighs up to the saved typing time.

Related

Is it possible to join an unrelated table in an EF Core Include query

I have an EF Core code first project and have created the following table
[Table("Activity")]
public class Activity : BaseModel
{
[Key]
public int Id { get; set; }
public int ActivityId { get; set; }
public TransactionType Type { get; set; }
public virtual ActivityItem Item { get; set; }
}
ActivityItem is an abstract class that doesn't map to its own table, but I have three separate models that all inherit from it.
//Not its own table in the database
public abstract class ActivityItem
{}
[Table("ClassA")]
public class ClassA : ActivityItem
{
public int Id {get; set;}
}
[Table("ClassB")]
public class ClassB : ActivityItem
{
public int Id {get; set;}
}
[Table("ClassC")]
public class ClassC : ActivityItem
{
public int Id {get; set;}
}
In this particular instance I'm explicitly avoiding the Table-Per-Hierarchy scheme because I need to have ClassA, ClassB, and ClassC as there own unique tables in the database.
Whenever I'm querying an entry from my Activity table I use the Type property to indicate if the Item is either an instance of ClassA, ClassB, or ClassC.
Is there a way that I can still use the Include method when I'm forming my queries to populate the Item navigation property?
Current Solution
var activity = _context.Activity.Where(...).FirstOrDefault();
if (activity.Type == "ClassA")
activity.Item = _context.ClassA.First(p => p.Id == activity.ActivityId);
else if (activity.Type == "ClassB")
activity.Item = _context.ClassB.First(p => p.Id == activity.ActivityId);
else if (activity.Type == "ClassC")
activity.Item = _context.ClassC.First(p => p.Id == activity.ActivityId);
Desired Solution
var activity = _context.Activity.Include(p => p.Item).Where(...).FirstOrDefault();
I know EF Core will write Join queries on the related table when you use the Include method, but in my case there is no related table since ActivityItem isn't represented in the database. Is there a way to explicitly specify which table to Join based on my custom Type field without resorting to the Table-Per-Hierarchy scheme?
So you have a table of Activities, and a predicate that filters zero or more Activities from this table. You are only interested in the first Activity that passes the filter. There might be no Activity at all that might pass the filter, hence the OrDefault.
This fetched Activity has a (string?) property Type. The value of this property depicts whether you should look in table ClassA / ClassB / ClassC.
The fetched Activity also has a property ActivityId. You want the first (or default) ActivityItem (ClassA / ClassB / ClassC), that has an Id equal to this ActivityId.
Your design makes that property ActivityId is not a proper foreign key to the ActivityItem that this Activity belongs to. In fact your foreign key is a combination of [Type, ActivityId].
So what you need, is a method that concatenates all your ClassA / ClassB / ClassC into one sequence, remembering the Type. After that you can join on [Type, Id].
If you have to do this only once, you can use the following. If you have to do this to solve several problems, consider creating extension methods.
var activityItems = dbContext.ClassAItems.Cast<ActivityItem>()
.Select(classAItem => new
{
Type = "ClassA",
Data = classAItem,
})
.Concat(dbContext.ClassBItems.Cast<ActivityItem>()
.Select(classBItem => new
{
Type = "ClassB",
Data = classBItem,
}))
.Concat(dbContext.ClassCItems.Cast<ActivityItem>()
.Select(classCItem => new
{
Type = "ClassC",
Data = classCItem,
}));
Now you can just do an inner join:
// join the filtered activities with the activityitems:
var result = dbContext.Activities.Where(...)
.Join(activityItems,
activity => new // from each activity take [type, activityId]
{
Type = activity.Type
Id = activity.ActivityId,
}
activityItem => new // from each ActivityItems take [Type, Id]
{
Type = activityItem.Type,
Id = activityItem.Data.Id,
}
// when these keys match, use the Activity and the matching ActivityItem to make one new
// well, in this case, you are only interested in the Data of the ActivityItem
(activity, activityItem) => activityItem.Data)
// From the joined items you only want the first:
.FirstOrDefault();

Entity Framework Core 2.2 use scalar DBFunction to get property on list of foreign keys

I have a model with a linked list of foreign keys i.e.
[Table("a"]
public class A {
[Key]
[Column("a_id")]
public int Id { get; set; }
public List<B> Bs { get; set; } = new List<B>();
}
[Table("b"]
public class B {
[Key]
[Column("b_id")]
public int Id { get; set; }
[NotMapped]
public string MyFunctionValue { get; set; }
[ForeignKey("a_id")]
public A A { get; set; }
}
I've then defined a function which links to a scalar sql function like so...
public static class MySqlFunctions {
[DbFunction("MyFunction", "dbo")]
public static string MyFunction(int bId) {
throw new NotImplementedException();
}
}
and registered in my context like so...
modelBuilder.HasDbFunction(() => MySqlFunctions.MyFunction(default));
What I want to be able to do in my repository class is to grab the A records with the linked B records in a List with their MyFunctionValue value set to the return value of the function when ran against the id of B. Something like...
myContext.A
.Include(a => a.Bs.Select(b => new B {
Id = b.Id,
MyFunctionValue = MySqlFunctions.MyFunction(b.Id)
});
However with all the options I've tried so far I'm getting either a InvalidOperationException or NotImplementedException I guess because it can't properly convert it to SQL?
Is there any way I can write a query like this or is it too complex for EF to generate SQL for? I know there's a possibility I could use .FromSql but I'd rather avoid it if possible as it's a bit messy.
EDIT:
So I've managed to get it working with the following code but it's obviously a bit messy, if anyone has a better solution I'd be grateful.
myContext.A
.Include(a => a.Bs)
.Select(a => new {
A = a,
MyFunctionValues = a.Bs.Select(b => MySqlFunctions.MyFunction(b.Id))
})
.AsEnumerable()
.Select(aWithMfvs => {
for (int i = 0; i < aWithMfvs.MyFunctionValues.Count(); i++) {
aWithMfvs.A.Bs[i].MyFunctionValue = aWithMfvs.MyFunctionValues[i];
}
return aWithMfvs.A;
})
.AsQueryable();
There are several things you should consider with db functions:
When you declare a DbFunction as static method, you don't have to register it with the modelBuilder
Registering is only needed, when you would use Fluent API (which IMHO I recommend anyway in order to have you entities free of any dependencies)
The return value, the method name and the count, type and order of the method parameters must match your code in the user defined function (UDF)
You named the method parameter as bId. Is it exactly the same in your UDF or rather as in the table like b_id?

How to correctly store a number of results in a DTO?

I have stored procedure attached to a DB which should return results from just a simple search. The query is added to my entity and calls a regular method. The problem I face is storing the results from this procedure to a particular DTO as a list.
Is there any way to effectively store the results from this stored procedure as a list to the DTO?
Below is what I have so far
Controller:
[Produces("application/json")]
[RoutePrefix("api/jobs")]
public class OutputController : ApiController
{
private TestCoastalToolsEntities _output;
public OutputController()
{
_output = new TestCoastalToolsEntities();
_output.Configuration.ProxyCreationEnabled = false;
}
/**Search**/
// POST: api/postsearch
[System.Web.Http.HttpPost, System.Web.Http.Route("postsearch")]
public async Task<IHttpActionResult> PostSearch(SearchInputDTO srequest)
{
OutputDTO<SearchInputDTO> output = new OutputDTO<SearchInputDTO>();
SearchInputDTO SearchInput = null;
var searchString = srequest.SearchValue.ToString();
SearchInput.Results = _output.searchLog2(searchString);
if (_oput != null)
{
output.Success = true;
output.Results = _SearchInput.Results;
var json = new JavaScriptSerializer().Serialize(output);
return Ok(json);
}
return Ok(_ot);
}
}
}
-------------------------------------
Search DTO:
namespace toolPortal.API.Data.DTO
{
public class SearchInputDTO
{
public List<object> Results { get; set; }
public SearchInputDTO(output output) {
this.ID = output.ID;
this.Name = output.Name;
this.Job = output.Job;
this.Start = output.Start;
this.End = output.End;
this.Logs = output.Logs;
}
}
}
The expected result is that the stored procedure runs and stores the list of results to SearchInputResults. From there, those results should be stored in another DTO to be passed off on the return.
With EF you will want to leverage Select() to map the entities to your DTO, though you will need to consider the entire structure of the DTO. For instance, what is the "Logs" data structure going to comprise of? Is it a single string value, a list of strings, or a list of log records?
Using Select() you need to leverage property setters, not a constructor accepting an entity.
So a pattern like this:
public class Entity
{
public string Field { get; set; }
}
public class Dto
{
public string Field { get; set; }
}
var dtos = context.Entities
.Where(x => x.IsActive)
.Select(x => new Dto
{
Field = x.Field
})
.ToList();
Looking at your example with the constructor:
public class Dto
{
public string Field { get; private set; }
public Dto(Entity entity)
{
Field = entity.Field;
}
}
var dtos = context.Entities
.Where(x => x.IsActive)
.Select(x => new Dto(x))
.ToList();
This doesn't work with EF & Select. EF can map to an object, but only via properties and a parameterless constructor. There is a hack around this to be aware of, but avoid if you do see it:
var dtos = context.Entities
.Where(x => x.IsActive)
.ToList()
.Select(x => new Dto(x))
.ToList();
With the extra ToList() before the select, the call will work because EF will execute the query and return the list of entities, then the Select() will be performed as a Linq2Object query. The reason you should avoid this is because EF will select all properties from the entity, where we should only pull back the properties we care about. It's also easy to fall into a lazy-load performance trap if your Dto constructor population starts iterating over related entities. Using Select to load just the fields you need from an entity and any related entities allows EF to build an efficient query for just the data needed without any lazy load traps.
Using AutoMapper you can simplify this by setting up the mapping from entity to DTO then leveraging ProjectTo<Dto>().
So, if you want a DTO to represent the results (such as a success flag, error message) with a collection of the results if successful:
[Serializable]
// Our results container.
public class SearchResultsDTO
{
public bool IsSuccessful { get; private set; } = false;
public string ErrorMessage { get; private set; }
public ICollection<SearchResultDTO> Results { get; private set; } = new List<SearchResultDTO>();
private SearchResultsDTO() {}
public static SearchResultsDTO Success(ICollection<SearchResultDTO> results)
{
var results = new SearchResultsDTO
{
IsSuccessful = true,
Results = results
};
return results;
}
public static SearchResultsDTO Failure(string errorMessage)
{
var results = new SearchResultsDTO
{
ErrorMessage = errorMessage
};
return results;
}
}
[Serializable]
public class SearchResultDTO
{
public int ID {get; set;}
public string Name {get; set;}
public string Job {get; set;}
public DateTime Start {get; set;}
public DateTime End {get; set;}
public ICollection<string> Logs {get; set;} = new List<string>();
}
then to populate these from a DbContext: (Inside a Repository or wherever reads the data)
using (var context = new SearchContext())
{
var results = context.Logs
.Where(x => x.Name.Contains(sRequest))
.Select(x => new SearchResultDTO
{
ID = x.ID,
Name = x.Name,
Job = x.Job,
Start = x.Start,
End = x.End,
Logs = x.LogLines.Select(y => y.Line).ToList(),
}).ToList();
var resultDto = SearchResultsDTO.Success(results);
return resultsDto;
}
This assumes that the log entry has a Job, name, start, end date/times, and then a list of "lines" or entries to display as "Logs". (Where the Log table has a related LogLine table for example with the one or more lines) This demonstrates how to leverage Select to map not only the log record into a DTO, but also to map related records into something like a collection of strings, or a collection of other DTOs can be done as well.
Once it selects the DTO, I have it fill a container DTO using static factory methods to populate either a successful read, or a failed read. (which can be set in an exception handler for example.) Alternatively you can just new up a container class and populate properties, use a constructor /w parameters, or just return the list of DTOs. The SearchResultsDTO container is not referenced within the EF query.

Automapper: Mapper.Map does not work in every build

I have very strange issue with AutoMapper
In my windows service, When I create mapping from IDataReader to List object, it works for very first time only when i run the service.
As soon as I stop the service and run it again, AutoMapper cannot able to Map. Below is my code:
Property Class
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
public string Number { get; set; }
}
Actual Implementation
var employeeData = DataHelper.ExecuteReader("Select Id, Name, Number from dbo.Employee");
var employees = new List<Employee>();
employees = employeeData.MapToList<List<Employee>>();
Generic Extension Method
public static T MapToList<T>(this DataTable reader) where T : class
{
Mapper.CreateMap<IDataReader, T>();
// Mapper.AssertConfigurationIsValid();
return Mapper.Map<IDataReader, T>(reader.CreateDataReader());
}
Apart from this, I have noticed that when I clean solution and run service again it starts working.
I am not able to Identify why it behaves like this.
Given your table's fields have the same name as Employee's properties, you should be able to do like this (without any explicit mapping config):
var employeeData = DataHelper.ExecuteReader("Select Id, Name, Number from dbo.Employee");
var employees = AutoMapper.Mapper.DynamicMap<IDataReader, List<Employee>>(employeeData.CreateDataReader());

Entity Framework 4.3 Attaching an object from a parameter of an Action of a Controller

I have a situation where I have an object that is loaded back from a form to MVC controller via an action. We do not use FormCollection, but the one that use directly the class.
[HttpPost]
public ActionResult AjaxUpdate(Customer customer) { ...
The Customer object contain an object called customer which seem to be updated but when using SaveDatabase() on the context simply doesn't work.
To make it works I had to use in the action:
myDbContext.Customers.Attach(customer)
//...Code here that set to the customer.SubObject a real object from the database so I am sure that the SubObject contain an id which is valid and the datacontext is aware of it...
myDbContext.Entry(customer).State = EntityState.Modified;
Still, I had an exception concerning the "Store update, insert, or delete statement affected an unexpected number of rows (0)" that I were able to remove by using:
Database.ObjectContext().Refresh(RefreshMode.ClientWins,customer);
So, to warp up my question, why do I have to Attach + change the state + call Refresh. Isn't there a better way to update an object that contain object that are referenced in an other table. I am using Code first Entity Framework (Poco object). Also, I do not like to use Refresh since it's hidden from my Databasecontext.
I've made a console test project with EF 4.3.1. The code is my guess what you mean with the commented line and your comments below the question (but my guess is probably wrong because the program doesn't reproduce your error):
You can copy the code into program.cs and add a reference to EF 4.3.1:
using System.Data;
using System.Data.Entity;
namespace EFUpdateTest
{
public class Customer
{
public int Id { get; set; }
public string Name { get; set; }
public int SubObjectId { get; set; }
public SubObject SubObject { get; set; }
}
public class SubObject
{
public int Id { get; set; }
public string Something { get; set; }
}
public class CustomerContext : DbContext
{
public DbSet<Customer> Customers { get; set; }
public DbSet<SubObject> SubObjects { get; set; }
}
class Program
{
static void Main(string[] args)
{
int customerId = 0;
int subObject1Id = 0;
int subObject2Id = 0;
using (var ctx = new CustomerContext())
{
// Create customer with subobject
var customer = new Customer { Name = "John" };
var subObject = new SubObject { Something = "SubObject 1" };
customer.SubObject = subObject;
ctx.Customers.Add(customer);
// Create a second subobject, not related to any customer
var subObject2 = new SubObject { Something = "SubObject 2" };
ctx.SubObjects.Add(subObject2);
ctx.SaveChanges();
customerId = customer.Id;
subObject1Id = subObject.Id;
subObject2Id = subObject2.Id;
}
// New context, simulate detached scenario -> MVC action
using (var ctx = new CustomerContext())
{
// Changed customer name
var customer = new Customer { Id = customerId, Name = "Jim" };
ctx.Customers.Attach(customer);
// Changed reference to another subobject
var subObject2 = ctx.SubObjects.Find(subObject2Id);
customer.SubObject = subObject2;
ctx.Entry(customer).State = EntityState.Modified;
ctx.SaveChanges();
// No exception here.
}
}
}
}
This works without exception. The question is: What is different in your code which could cause the error?
Edit
To your comment that you don't have a foreign key property SubObjectId in the customer class: If I remove the property in the example program above I can reproduce the error.
The solution is to load the original subobject from the database before you change the relationship:
// Changed customer name
var customer = new Customer { Id = customerId, Name = "Jim" };
ctx.Customers.Attach(customer);
// Load original SubObject from database
ctx.Entry(customer).Reference(c => c.SubObject).Load();
// Changed reference to another subobject
var subObject2 = ctx.SubObjects.Find(subObject2Id);
customer.SubObject = subObject2;
ctx.Entry(customer).State = EntityState.Modified;
ctx.SaveChanges();
// No exception here.
Without a foreign key property you have an Independent Association which requires that the object including all references must represent the state in the database before you change it. If you don't set the reference of SubObject in customer EF assumes that the original state in the database is that customer does not refer to any subobject. The generated SQL for the UPDATE statement contains a WHERE clause like this:
WHERE [Customers].[Id] = 1 AND [Customers].[SubObject_Id] IS NULL
If the customer has a subobject in the DB [SubObject_Id] is not NULL, the condition is not fulfilled and the UPDATE does not happen (or happens for the "unexpected number of rows 0").
The problem does not occur if you have a foreign key property (Foreign Key Association): The WHERE clause in this case is only:
WHERE [Customers].[Id] = 1
So, it doesn't matter what's the original value of SubObject and of SubObjectId. You can leave the values null and the UPDATE works nonetheless.
Hence, the alternative solution to loading the original subobject is to introduce a foreign key property in Customer:
public int SubObjectId { get; set; }
Or, in case the relationship is not required:
public int? SubObjectId { get; set; }

Categories