What's the difference between [Computed] and [Write(false)] attributes? - c#

This resource explains how Computed excludes a property (in an update only?).
Specifie the property should be excluded from update.
[Table("Invoice")]
public class InvoiceContrib
{
[Key]
public int InvoiceID { get; set; }
public string Code { get; set; }
public InvoiceKind Kind { get; set; }
[Write(false)]
[Computed]
public string FakeProperty { get; set; }
}
using (var connection = My.ConnectionFactory())
{
connection.Open();
var invoices = connection.GetAll<InvoiceContrib>().ToList();
// The FakeProperty is skipped
invoices.ForEach(x => x.FakeProperty += "z");
var isSuccess = connection.Update(invoices);
}
Doesn't Write(false) fulfill the same purpose though? What's the difference between [Computed] and [Write(false)]?
Edit:
I've just checked the resource linked in response to my question. It almost hits the nail on this! Could someone please confirm if both attributes perform the same operations, but are just worded in two different ways, as to give a better abstraction to their users?

Both [Computed] and Write(false) will ignore the property while INSERT as well as UPDATE operations. So, both of them are same. You can use any one of it.
Documentation says below:
[Write(true/false)] - this property is (not) writeable
[Computed] - this property is computed and should not be part of updates
About Write:
As stated in first line in document above, Write handles "writeable" behavior. This should include both INSERT and UPDATE.
This can also be confirmed in source code here:
var properties = type.GetProperties().Where(IsWriteable).ToArray();
...
...
...
private static bool IsWriteable(PropertyInfo pi)
{
var attributes = pi.GetCustomAttributes(typeof(WriteAttribute), false).AsList();
if (attributes.Count != 1) return true;
var writeAttribute = (WriteAttribute)attributes[0];
return writeAttribute.Write;
}
About Computed:
Second line in document above is bit broad though.
should not be part of updates
Does that mean it can be the part of INSERT? No, it does not; it also cover both the actions. This can be observed with below code:
CREATE TABLE TestTable
(
[ID] [INT] IDENTITY (1,1) NOT NULL CONSTRAINT TestTable_P_KEY PRIMARY KEY,
[Name] [VARCHAR] (100) NOT NULL,
[ComputedCol] [VARCHAR] (100) NOT NULL DEFAULT '',
[NonWriteCol] [VARCHAR] (100) NOT NULL DEFAULT ''
)
[Table("TestTable")]
public class MyTable
{
[Key]
public int ID { get; set; }
public string Name { get; set; }
[Computed]
public string ComputedCol { get; set; }
[Write(false)]
public string NonWriteCol { get; set; }
}
int id;
using(SqlConnection conn = new SqlConnection(#"connection string"))
{
MyTable myTable = new MyTable();
myTable.Name = "Name";
myTable.ComputedCol = "computed";
myTable.NonWriteCol = "writable";
conn.Insert<MyTable>(myTable);
id = myTable.ID;
}
using(SqlConnection conn = new SqlConnection(#"connection string"))
{
MyTable myTable = conn.Get<MyTable>(id);
myTable.Name = "Name_1";
myTable.ComputedCol = "computed_1";
myTable.NonWriteCol = "writable_1";
conn.Update<MyTable>(myTable);
}
With above code, you will observe that no matter which attribute you choose to decorate the property, it will neither be considered for INSERT nor for UPDATE. So basically, both the attributes are playing same role.
This can be further confirmed in Dapper.Tests.Contrib test project on github.
[Table("Automobiles")]
public class Car
{
public int Id { get; set; }
public string Name { get; set; }
[Computed]
public string Computed { get; set; }
}
...
...
...
//insert with computed attribute that should be ignored
connection.Insert(new Car { Name = "Volvo", Computed = "this property should be ignored" });
Source: 1 and 2
Looking at the comment and the value assigned to the property in above code, it makes clear that Computed should also ignore the property for INSERT operation; it is expected result of the test.
Why those two ways are provided for same purpose is not known. It causes confusion.
Following are some additional references:
Comment 1
I use [Computed] or [Write("False")] for that. Does that not work for your scenario?
Comment 2
Glad I could help. Every day is a school day! I'm not sure why they both exist though as I think they are functionally the same. I tend to use [Computed] just because it is marginally easier to type.
Comment 3
I understand that using Dapper.Contrib I can use the Write and Computed attributes to ignore properties during write operations. However, this will ignore the properties on both insert and update. I need a way to ignore properties on updates. My suggestion would be to add 2 attributes... perhaps named Insertable(bool) and Updateable(bool). When a false value is passed to these the framework would exclude that property for the given operation. This is a lightweight, straightforward approach to a very common problem.
I don't think Computed attribute has anything to do with Computed Columns as Dapper.Contrib support multiple RDBMS.

Related

Entity Framework database-first approach conditionally insert data

Using Entity Framework, how can I insert data if it does not exist, and update a field if it does?
public class Rootobject
{
public string odatacontext { get; set; }
public Value[] value { get; set; }
}
public class Value
{
public int AccountId { get; set; }
public DateTime? SubmissionDate { get; set; }
public string Status { get; set; }
}
To retrieve all the data from my API I use
root.value.Select(x => new satiaL
{
accountID = x.AccountID,
subDate = x.SubmissionDate,
x_status = x.Status
});
which of course will insert all records.
If the AccountID already exists in the database, I want to update the value of x_status, but if the AccountID does NOT yet exist in the database, then I want to insert all values.
You can not.
Upsert functionality is not part of an object/relational model - objects are there or not, and tracked by identity. Thre is no "update if it is not there" concept - at all. So, there is nothing for EfCore to implement.
This smells like abusing an ORM as a ETL loader, and this is not what you should do - ETL (mass data loading) is not what and ORM is made for. Time to write your own method to move data up into tables and possibly do upswert there. Did that years ago, comes really handy at times.
Right now all you can do is run a lot of finds for every account and basicalyl write code: create if not exists, update if exists.
Pseudocode:
var account = Find ( select ) or default from db
if account == null create
else update
savechanges
Something along this line. Beware of performance - you may want to just builk load all accounts. Beware of conflicting updates.

How to perform strict mapping on Dapper

I am using dapper to map SQL result set directly to my C# object, everything works nicely.
I am using statements like this to do the mapping
var result = connection.Query< MyClass >( "sp_select", );
but this statement doesn't seem to enforce exact mapping between the class fields and the columns returned from the database. Meaning, it won't fail when the field on the POCO doesn't exist on the result set.
I do enjoy the fact that the implementation is loose and doesn't enforce any restriction right of the bat, but is there any feature of dapper that would allow me to demand certain fields from the result set before deeming the mapping successful?
You can also try Dapper-Extensions
Here is an example:
public class Person
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Address { get; set; }
}
[TestFixture]
public class DapperExtensions
{
private SqlConnection _connection;
[SetUp]
public void Init()
{
_connection = new SqlConnection(#"Data Source=.\sqlexpress;Integrated Security=true; Initial Catalog=mydb");
_connection.Open();
_connection.Execute("create table Person(Id int not null, FirstName varchar(100) not null, LastName varchar(100) not null)");
_connection.Execute("insert into Person(Id, FirstName, LastName) values (1, 'Bill', 'Gates')");
}
[TearDown]
public void Teardown()
{
_connection.Execute("drop table Person");
_connection.Close();
}
[Test]
public void Test()
{
var result = _connection.Get<Person>(1);
}
}
The test will fail due to a missing Address column in the Person table.
You can also ignore columns with Custom Maps:
public class PersonMapper : ClassMapper<Person>
{
public PersonMapper()
{
Map(x => x.Address).Ignore();
AutoMap();
}
}
There is no way for you to enforce this "automagically" with an attribute or a flag. You can follow this open Github issue for more background.
This could be accomplished by you manually by mapping each property yourself in a select clause, although at that point you've lost a lot of the power and ease of use of Dapper.
var result = connection.Query<MyClass>("sp_select")
.Select(x =>
{
// manually map each property and verify
// that the data is returned
});

Generic Add with Retrieve

I'm trying to create a generic Add that will return the actual values from the DB, because some of the values might be calculated by SQL.
For example:
public partial class Customer
{
public string ClientNum { get; set; }
public string ClientName { get; set; }
public string CompanyName { get; set; }
public string Adress { get; set; }
public System.Guid SysRowID { get; set; }
}
SysRowId is calulated at SQL as newid(). So, after inserting the new record I want to do a Find. How may I do that in a generic way?
So far I have this:
var newDBRow = CreateDBRow(tableIndex);
FillValues(newDBRow);
Db.Set(newDBRow.GetType()).Add(newDBRow);
Db.SaveChanges();
var entry = Db.Entry(newDBRow); //SysRowID is blank at CurrentValues
newDBRow = Db.Set(newDBRow.GetType()).Find(KeysNeededHere); //Unable to get the entity keys.
I tried to use the entry, but SysRowID is still blank at CurrentValues. Also, I tried to use the Find but it needs keys and I can't add the ClientNum since I want to do it in a generic way for all entities.
DbSet.Find method expect the value of the key. If SysRowID is not defined as your key this method will never return a value even if SysRowID has the correct value.
Database First:
If you are using Database First, then in your EDMX model just right click on your property SysRowID and click on Properties, then change the StoreGeneratedPattern value to Computed.
Code First:
If you are using Code First approach then you must decorate your property SysRowID with DatabaseGenerated attribute and pass DatabaseGeneratedOption.Computedas a parameter to the constructor of the attribute. At the end you will have this code on your property
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public System.Guid? SysRowID { get; set; }
By doing this either you are in Code First or Database First, EF will know that this property is computed by the database and it will retrieve it after insert success.

ASP.NET MVC Many-to-Many Relationship (Maintaining insertion order)

I've defined two tables in SQL: "Inquerito" and "Pergunta", and a third table "Inquerito_Pergunta" to make the many-to-many relationship. In that last table, the primary key is both the primary key of the Inquerito and the Pergunta.
I'am supposed to add as many as "Perguntas" as I want into an "Inquerito" instance. And, it's important to keep the insertion order, so when I'm showing it to the user it's shown in the same order. A "Pergunta" can also have multiple "Inquerito", but the order doesn't matter in that case.
I'm using MVC 4 Entity Framework and my Models are defined like this:
public partial class Inquerito
{
public Inquerito()
{
this.Pergunta = new List<Pergunta>();
}
public System.Guid id { get; set; }
public virtual ICollection<Pergunta> Pergunta { get; set; }
}
public partial class Pergunta
{
public Pergunta()
{
this.Inquerito = new List<Inquerito>();
}
public System.Guid id { get; set; }
public virtual ICollection<Inquerito> Inquerito { get; set; }
}
As you can see I've already changed the default HashSet to a List.
To save all stuff to the database I do:
inquerito.Pergunta.Add(pergunta);
db.Pergunta.Add(pergunta);
db.Inquerito.Add(inquerito);
The problem is the insertion order is lost.
After adding all "Pergunta" that I want I do:
// Grava alterações e desconecta da base de dados.
db.SaveChanges();
Inquerito inquerito1 = db.Inquerito.Find(inquerito.id);
if (inquerito1 != null)
{
foreach (Pergunta p in inquerito1.Pergunta.ToList())
{
System.Diagnostics.Debug.WriteLine("__pergunta: " + p.descricao);
}
}
db = new quest_geralEntities();
inquerito1 = db.Inquerito.Find(inquerito.id);
if (inquerito1 != null)
{
foreach (Pergunta p in inquerito1.Pergunta.ToList())
{
System.Diagnostics.Debug.WriteLine("__pergunta: " + p.descricao);
}
}
So, the first time I print all the "Pergunta" linked to that "Inquerito" everything is shown in the right order (insertion order), but when I update the context, with: "new quest_geralEntities()" when I print it again the insertion order is completely lost.
I've been struggling with this problem for several hours now and I can't find a solution. I hope I've been clear enough to be helped.
Thanks.
If you want to maintain insertion order, I recommend using a field containing an int that increments. You can add an int identity column without it being the primary key and use that column to sort on, maintaining your insertion order.
You can try to change the property type of your relation :
public virtual IList<Inquerito> Inquerito { get; set; }
public virtual IList<Pergunta> Pergunta { get; set; }
You'll need to regenerate the database schema. I'm not sure if it's working on EF, but in NHibernate, it works.
Hope it helps !

Easier way of avoiding duplicates in entity framework

Can anyone provide an easier more automatic way of doing this?
I have the following save method for a FilterComboTemplate model. The data has been converted from json to a c# model entity by the webapi.
So I don't create duplicate entries in the DeviceProperty table I have to go through each filter in turn and retrieve the assigned DeviceFilterProperty from the context and override the object in the filter. See the code below.
I have all the object Id's if they already exist so it seems like this should be handled automatically but perhaps that's just wishful thinking.
public void Save(FilterComboTemplate comboTemplate)
{
// Set the Device Properties so we don't create dupes
foreach (var filter in comboTemplate.Filters)
{
filter.DeviceProperty = context.DeviceFilterProperties.Find(filter.DeviceFilterProperty.DeviceFilterPropertyId);
}
context.FilterComboTemplates.Add(comboTemplate);
context.SaveChanges();
}
From here I'm going to have to check whether any of the filters exist too and then manually update them if they are different to what's in the database so as not to keep creating a whole new set after an edit of a FilterComboTemplate.
I'm finding myself writing a lot of this type of code. I've included the other model classes below for a bit of context.
public class FilterComboTemplate
{
public FilterComboTemplate()
{
Filters = new Collection<Filter>();
}
[Key]
public int FilterComboTemplateId { get; set; }
[Required]
public string Name { get; set; }
[Required]
public ICollection<Filter> Filters { get; set; }
}
public class Filter
{
[Key]
public int FilterId { get; set; }
[Required]
public DeviceFilterProperty DeviceFilterProperty { get; set; }
[Required]
public bool Exclude { get; set; }
[Required]
public string Data1 { get; set; }
}
public class DeviceFilterProperty
{
[Key]
public int DeviceFilterPropertyId { get; set; }
[Required]
public string Name { get; set; }
}
Judging from some similar questions on SO, it does not seem something EF does automatically...
It's probably not a massive cut on code but you could do something like this, an extension method on DbContext (or on your particular dataContext):
public static bool Exists<TEntity>(this MyDataContext context, int id)
{
// your code here, something similar to
return context.Set<TEntity>().Any(x => x.Id == id);
// or with reflection:
return context.Set<TEntity>().Any(x => {
var props = typeof(TEntity).GetProperties();
var myProp = props.First(y => y.GetCustomAttributes(typeof(Key), true).length > 0)
var objectId = myProp.GetValue(x)
return objectId == id;
});
}
This will check if an object with that key exists in the DbContext. Naturally a similar method can be created to actually return that entity as well.
There are two "returns" in the code, just use the one you prefer. The former will force you to have all entities inherit from an "Entity" object with an Id Property (which is not necessarily a bad thing, but I can see the pain in this... you will also need to force the TEntity param: where TEntity : Entity or similar).
Take the "reflection" solution with a pinch of salt, first of all the performance may be a problem, second of all I don't have VS running up now, so I don't even know if it compiles ok, let alone work!
Let me know if that works :)
It seems that you have some common operations for parameters after it's bound from request.
You may consider to write custom parameter bindings to reuse the code. HongMei's blog is a good start point: http://blogs.msdn.com/b/hongmeig1/archive/2012/09/28/how-to-customize-parameter-binding.aspx
You may use the code in Scenario 2 to get the formatter binding to deserialize the model from body and perform the operations your want after that.
See the final step in the blog to specify the parameter type you want customize.

Categories